diff --git a/src/extensions/extension-loader/extension-loader.ts b/src/extensions/extension-loader/extension-loader.ts index db3e924209..00fb397079 100644 --- a/src/extensions/extension-loader/extension-loader.ts +++ b/src/extensions/extension-loader/extension-loader.ts @@ -264,7 +264,6 @@ export class ExtensionLoader { registries.StatusBarRegistry.getInstance().add(extension.statusBarItems), registries.CommandRegistry.getInstance().add(extension.commands), registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems), - registries.TopBarRegistry.getInstance().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 2ccb2cfe7a..9f7b947d82 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -26,6 +26,7 @@ import type { CatalogEntity } from "../common/catalog"; import type { Disposer } from "../common/utils"; import { catalogEntityRegistry, EntityFilter } from "../renderer/api/catalog-entity-registry"; import { catalogCategoryRegistry, CategoryFilter } from "../renderer/api/catalog-category-registry"; +import type { TopBarRegistration } from "../renderer/components/layout/top-bar/top-bar-registration"; import type { KubernetesCluster } from "../common/catalog-entities"; import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/welcome-menu-items/welcome-menu-registration"; import type { WelcomeBannerRegistration } from "../renderer/components/+welcome/welcome-banner-items/welcome-banner-registration"; @@ -45,7 +46,7 @@ export class LensRendererExtension extends LensExtension { welcomeMenus: WelcomeMenuRegistration[] = []; welcomeBanners: WelcomeBannerRegistration[] = []; catalogEntityDetailItems: registries.CatalogEntityDetailRegistration[] = []; - topBarItems: registries.TopBarRegistration[] = []; + 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 2cd822f77a..76f6c05d11 100644 --- a/src/extensions/registries/index.ts +++ b/src/extensions/registries/index.ts @@ -32,5 +32,4 @@ export * from "./command-registry"; export * from "./entity-setting-registry"; export * from "./catalog-entity-detail-registry"; export * from "./workloads-overview-detail-registry"; -export * from "./topbar-registry"; export * from "./protocol-handler"; diff --git a/src/renderer/components/+welcome/__test__/welcome.test.tsx b/src/renderer/components/+welcome/__test__/welcome.test.tsx index f7ae85bfea..74cf65b20d 100644 --- a/src/renderer/components/+welcome/__test__/welcome.test.tsx +++ b/src/renderer/components/+welcome/__test__/welcome.test.tsx @@ -24,7 +24,6 @@ import { screen } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; import { defaultWidth, Welcome } from "../welcome"; import { computed } from "mobx"; -import { TopBarRegistry } from "../../../../extensions/registries"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import type { DiRender } from "../../test-utils/renderFor"; import { renderFor } from "../../test-utils/renderFor"; @@ -62,12 +61,6 @@ describe("", () => { }), ]), ); - - TopBarRegistry.createInstance(); - }); - - afterEach(() => { - TopBarRegistry.resetInstance(); }); it("renders registered in WelcomeBannerRegistry and hide logo", async () => { diff --git a/src/renderer/components/cluster-manager/cluster-manager.tsx b/src/renderer/components/cluster-manager/cluster-manager.tsx index 04af444c30..300c9b2eb4 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.tsx +++ b/src/renderer/components/cluster-manager/cluster-manager.tsx @@ -39,8 +39,8 @@ import { DeleteClusterDialog } from "../delete-cluster-dialog"; import { reaction } from "mobx"; import { navigation } from "../../navigation"; import { setEntityOnRouteMatch } from "../../../main/catalog-sources/helpers/general-active-sync"; -import { TopBar } from "../layout/topbar"; import { catalogURL, getPreviousTabUrl } from "../../../common/routes"; +import { TopBar } from "../layout/top-bar/top-bar"; @observer export class ClusterManager extends React.Component { diff --git a/src/renderer/components/layout/top-bar/top-bar-items/top-bar-items.injectable.ts b/src/renderer/components/layout/top-bar/top-bar-items/top-bar-items.injectable.ts new file mode 100644 index 0000000000..9c5bd3d1e8 --- /dev/null +++ b/src/renderer/components/layout/top-bar/top-bar-items/top-bar-items.injectable.ts @@ -0,0 +1,37 @@ +/** + * 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 { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import rendererExtensionsInjectable from "../../../../../extensions/renderer-extensions.injectable"; + +const topBarItemsInjectable = getInjectable({ + instantiate: (di) => { + const extensions = di.inject(rendererExtensionsInjectable); + + return computed(() => + extensions.get().flatMap((extension) => extension.topBarItems), + ); + }, + + lifecycle: lifecycleEnum.singleton, +}); + +export default topBarItemsInjectable; diff --git a/src/extensions/registries/topbar-registry.ts b/src/renderer/components/layout/top-bar/top-bar-registration.d.ts similarity index 88% rename from src/extensions/registries/topbar-registry.ts rename to src/renderer/components/layout/top-bar/top-bar-registration.d.ts index 37b55faaaa..5e470d4d26 100644 --- a/src/extensions/registries/topbar-registry.ts +++ b/src/renderer/components/layout/top-bar/top-bar-registration.d.ts @@ -18,10 +18,6 @@ * 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; } @@ -29,6 +25,3 @@ interface TopBarComponents { export interface TopBarRegistration { components: TopBarComponents; } - -export class TopBarRegistry extends BaseRegistry { -} diff --git a/src/renderer/components/layout/__tests__/topbar-win-linux.test.tsx b/src/renderer/components/layout/top-bar/top-bar-win-linux.test.tsx similarity index 83% rename from src/renderer/components/layout/__tests__/topbar-win-linux.test.tsx rename to src/renderer/components/layout/top-bar/top-bar-win-linux.test.tsx index 1307e181cd..6d8014004e 100644 --- a/src/renderer/components/layout/__tests__/topbar-win-linux.test.tsx +++ b/src/renderer/components/layout/top-bar/top-bar-win-linux.test.tsx @@ -20,23 +20,29 @@ */ import React from "react"; -import { render, fireEvent } from "@testing-library/react"; +import { fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; -import { TopBar } from "../topbar"; -import { TopBarRegistry } from "../../../../extensions/registries"; +import { TopBar } from "./top-bar"; import { IpcMainWindowEvents } from "../../../../main/window-manager"; import { broadcastMessage } from "../../../../common/ipc"; import * as vars from "../../../../common/vars"; +import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import { DiRender, renderFor } from "../../test-utils/renderFor"; -const mockConfig = vars as { isWindows: boolean, isLinux: boolean }; +const mockConfig = vars as { isWindows: boolean; isLinux: boolean }; jest.mock("../../../../common/ipc"); jest.mock("../../../../common/vars", () => { + const SemVer = require("semver").SemVer; + + const versionStub = new SemVer("1.0.0"); + return { __esModule: true, isWindows: null, isLinux: null, + appSemVer: versionStub, }; }); @@ -57,20 +63,20 @@ jest.mock("@electron/remote", () => { }; }); -describe(" in Windows and Linux", () => { - beforeEach(() => { - TopBarRegistry.createInstance(); - }); +describe(" in Windows and Linux", () => { + let render: DiRender; - afterEach(() => { - TopBarRegistry.resetInstance(); + beforeEach(() => { + const di = getDiForUnitTesting(); + + render = renderFor(di); }); it("shows window controls on Windows", () => { mockConfig.isWindows = true; mockConfig.isLinux = false; - const { getByTestId } = render(); + const { getByTestId } = render(); expect(getByTestId("window-menu")).toBeInTheDocument(); expect(getByTestId("window-minimize")).toBeInTheDocument(); @@ -82,7 +88,7 @@ describe(" in Windows and Linux", () => { mockConfig.isWindows = false; mockConfig.isLinux = true; - const { getByTestId } = render(); + const { getByTestId } = render(); expect(getByTestId("window-menu")).toBeInTheDocument(); expect(getByTestId("window-minimize")).toBeInTheDocument(); @@ -93,7 +99,7 @@ describe(" in Windows and Linux", () => { it("triggers ipc events on click", () => { mockConfig.isWindows = true; - const { getByTestId } = render(); + const { getByTestId } = render(); const menu = getByTestId("window-menu"); const minimize = getByTestId("window-minimize"); diff --git a/src/renderer/components/layout/topbar.module.scss b/src/renderer/components/layout/top-bar/top-bar.module.scss similarity index 100% rename from src/renderer/components/layout/topbar.module.scss rename to src/renderer/components/layout/top-bar/top-bar.module.scss diff --git a/src/renderer/components/layout/__tests__/topbar.test.tsx b/src/renderer/components/layout/top-bar/top-bar.test.tsx similarity index 84% rename from src/renderer/components/layout/__tests__/topbar.test.tsx rename to src/renderer/components/layout/top-bar/top-bar.test.tsx index 110ddef800..a2a93c0c4c 100644 --- a/src/renderer/components/layout/__tests__/topbar.test.tsx +++ b/src/renderer/components/layout/top-bar/top-bar.test.tsx @@ -20,14 +20,23 @@ */ import React from "react"; -import { render, fireEvent } from "@testing-library/react"; +import { fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; -import { TopBar } from "../topbar"; -import { TopBarRegistry } from "../../../../extensions/registries"; +import { TopBar } from "./top-bar"; +import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable"; +import { DiRender, renderFor } from "../../test-utils/renderFor"; +import topBarItemsInjectable from "./top-bar-items/top-bar-items.injectable"; +import { computed } from "mobx"; jest.mock("../../../../common/vars", () => { + const SemVer = require("semver").SemVer; + + const versionStub = new SemVer("1.0.0"); + return { isMac: true, + appSemVer: versionStub, }; }); @@ -76,12 +85,13 @@ jest.mock("@electron/remote", () => { }); describe("", () => { - beforeEach(() => { - TopBarRegistry.createInstance(); - }); + let di: ConfigurableDependencyInjectionContainer; + let render: DiRender; - afterEach(() => { - TopBarRegistry.resetInstance(); + beforeEach(() => { + di = getDiForUnitTesting(); + + render = renderFor(di); }); it("renders w/o errors", () => { @@ -129,13 +139,13 @@ describe("", () => { const testId = "testId"; const text = "an item"; - TopBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [ + di.override(topBarItemsInjectable, () => computed(() => [ { components: { Item: () => {text}, }, }, - ]); + ])); const { getByTestId } = render(); diff --git a/src/renderer/components/layout/topbar.tsx b/src/renderer/components/layout/top-bar/top-bar.tsx similarity index 76% rename from src/renderer/components/layout/topbar.tsx rename to src/renderer/components/layout/top-bar/top-bar.tsx index 4705c6993c..a0a785666b 100644 --- a/src/renderer/components/layout/topbar.tsx +++ b/src/renderer/components/layout/top-bar/top-bar.tsx @@ -19,22 +19,28 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./topbar.module.scss"; +import styles from "./top-bar.module.scss"; import React, { useEffect, useMemo, useRef } from "react"; import { observer } from "mobx-react"; -import { TopBarRegistry } from "../../../extensions/registries"; -import { Icon } from "../icon"; +import type { IComputedValue } from "mobx"; +import { Icon } from "../../icon"; import { webContents, getCurrentWindow } from "@electron/remote"; import { observable } from "mobx"; -import { broadcastMessage, ipcRendererOn } from "../../../common/ipc"; -import { watchHistoryState } from "../../remote-helpers/history-updater"; -import { isActiveRoute, navigate } from "../../navigation"; -import { catalogRoute, catalogURL } from "../../../common/routes"; -import { IpcMainWindowEvents } from "../../../main/window-manager"; -import { isLinux, isWindows } from "../../../common/vars"; -import { cssNames } from "../../utils"; +import { broadcastMessage, ipcRendererOn } from "../../../../common/ipc"; +import { watchHistoryState } from "../../../remote-helpers/history-updater"; +import { isActiveRoute, navigate } from "../../../navigation"; +import { catalogRoute, catalogURL } from "../../../../common/routes"; +import { IpcMainWindowEvents } from "../../../../main/window-manager"; +import { isLinux, isWindows } from "../../../../common/vars"; +import { cssNames } from "../../../utils"; +import topBarItemsInjectable from "./top-bar-items/top-bar-items.injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { TopBarRegistration } from "./top-bar-registration"; -interface Props extends React.HTMLAttributes { +interface Props extends React.HTMLAttributes {} + +interface Dependencies { + items: IComputedValue; } const prevEnabled = observable.box(false); @@ -48,34 +54,10 @@ ipcRendererOn("history:can-go-forward", (event, state: boolean) => { nextEnabled.set(state); }); -export const TopBar = observer(({ children, ...rest }: Props) => { +const NonInjectedTopBar = (({ items, children, ...rest }: Props & Dependencies) => { const elem = useRef(); const window = useMemo(() => getCurrentWindow(), []); - const renderRegisteredItems = () => { - const items = TopBarRegistry.getInstance().getItems(); - - if (!Array.isArray(items)) { - return null; - } - - return ( -

- {items.map((registration, index) => { - if (!registration?.components?.Item) { - return null; - } - - return ( -
- -
- ); - })} -
- ); - }; - const openContextMenu = () => { broadcastMessage(IpcMainWindowEvents.OPEN_CONTEXT_MENU); }; @@ -156,7 +138,7 @@ export const TopBar = observer(({ children, ...rest }: Props) => { />
- {renderRegisteredItems()} + {renderRegisteredItems(items.get())} {children} {(isWindows || isLinux) && (
@@ -174,3 +156,29 @@ export const TopBar = observer(({ children, ...rest }: Props) => {
); }); + +const renderRegisteredItems = (items: TopBarRegistration[]) => ( +
+ {items.map((registration, index) => { + if (!registration?.components?.Item) { + return null; + } + + return ( +
+ +
+ ); + })} +
+); + + + +export const TopBar = withInjectables(observer(NonInjectedTopBar), { + getProps: (di, props) => ({ + items: di.inject(topBarItemsInjectable), + + ...props, + }), +}); diff --git a/src/renderer/initializers/registries.ts b/src/renderer/initializers/registries.ts index 3130fe6984..a27c47153b 100644 --- a/src/renderer/initializers/registries.ts +++ b/src/renderer/initializers/registries.ts @@ -34,5 +34,4 @@ export function initRegistries() { registries.KubeObjectStatusRegistry.createInstance(); registries.StatusBarRegistry.createInstance(); registries.WorkloadsOverviewDetailRegistry.createInstance(); - registries.TopBarRegistry.createInstance(); }