diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index 6adba1a987..0822ed331a 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -244,6 +244,7 @@ export class WindowManager extends Singleton { this.sendToView({ channel: IpcRendererNavigationEvents.RELOAD_PAGE, frameInfo }); } else { webContents.getFocusedWebContents()?.reload(); + webContents.getFocusedWebContents()?.clearHistory(); } } diff --git a/src/renderer/components/+catalog/catalog-menu.module.css b/src/renderer/components/+catalog/catalog-menu.module.css index c72dd5bb53..84584e089d 100644 --- a/src/renderer/components/+catalog/catalog-menu.module.css +++ b/src/renderer/components/+catalog/catalog-menu.module.css @@ -27,4 +27,9 @@ @apply uppercase font-bold; color: var(--textColorAccent); font-size: small; +} + +.catalog { + @apply p-5 font-bold text-2xl; + color: var(--textColorAccent); } \ No newline at end of file diff --git a/src/renderer/components/+catalog/catalog-menu.tsx b/src/renderer/components/+catalog/catalog-menu.tsx index 78052cbd9d..0e3d54094e 100644 --- a/src/renderer/components/+catalog/catalog-menu.tsx +++ b/src/renderer/components/+catalog/catalog-menu.tsx @@ -58,6 +58,7 @@ export function CatalogMenu(props: Props) { // Overwrite Material UI styles with injectFirst https://material-ui.com/guides/interoperability/#controlling-priority-4
+
Catalog
} diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index 4256170649..658c91273f 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -43,7 +43,7 @@ import { catalogURL, CatalogViewRouteParam } from "../../../common/routes"; import { CatalogMenu } from "./catalog-menu"; import { HotbarIcon } from "../hotbar/hotbar-icon"; import { RenderDelay } from "../render-delay/render-delay"; -import { CatalogTopbar } from "../cluster-manager/catalog-topbar"; +import { TopBar } from "../layout/topbar"; export const previousActiveTab = createAppStorage("catalog-previous-active-tab", ""); @@ -247,7 +247,7 @@ export class Catalog extends React.Component { return ( <> - +
{ this.renderList() } diff --git a/src/renderer/components/+welcome/__test__/welcome.test.tsx b/src/renderer/components/+welcome/__test__/welcome.test.tsx index 6eab098d05..d2eda315c4 100644 --- a/src/renderer/components/+welcome/__test__/welcome.test.tsx +++ b/src/renderer/components/+welcome/__test__/welcome.test.tsx @@ -26,6 +26,15 @@ import { Welcome } from "../welcome"; import { TopBarRegistry, WelcomeMenuRegistry, WelcomeBannerRegistry } from "../../../../extensions/registries"; import { defaultWidth } from "../welcome"; +jest.mock( + "electron", + () => ({ + ipcRenderer: { + on: jest.fn(), + }, + }) +); + describe("", () => { beforeEach(() => { TopBarRegistry.createInstance(); diff --git a/src/renderer/components/+welcome/welcome.tsx b/src/renderer/components/+welcome/welcome.tsx index d2ec4fea8c..a4cff08369 100644 --- a/src/renderer/components/+welcome/welcome.tsx +++ b/src/renderer/components/+welcome/welcome.tsx @@ -26,8 +26,8 @@ import Carousel from "react-material-ui-carousel"; import { Icon } from "../icon"; import { productName, slackUrl } from "../../../common/vars"; import { WelcomeMenuRegistry } from "../../../extensions/registries"; -import { WelcomeTopbar } from "../cluster-manager/welcome-topbar"; import { WelcomeBannerRegistry } from "../../../extensions/registries"; +import { TopBar } from "../layout/topbar"; export const defaultWidth = 320; @@ -49,7 +49,7 @@ export class Welcome extends React.Component { return ( <> - +
{welcomeBanner.length > 0 ? ( diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 46e285969a..3f8dbf7d8b 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -72,6 +72,7 @@ import { catalogEntityRegistry } from "../api/catalog-entity-registry"; import { getHostedClusterId } from "../utils"; import { ClusterStore } from "../../common/cluster-store"; import type { ClusterId } from "../../common/cluster-types"; +import { watchHistoryState } from "../remote-helpers/history-updater"; @observer export class App extends React.Component { @@ -128,7 +129,9 @@ export class App extends React.Component { disposeOnUnmount(this, [ kubeWatchApi.subscribeStores([podsStore, nodesStore, eventStore, namespaceStore], { preload: true, - }) + }), + + watchHistoryState() ]); } diff --git a/src/renderer/components/cluster-manager/catalog-topbar.tsx b/src/renderer/components/cluster-manager/catalog-topbar.tsx deleted file mode 100644 index 7e70834ae3..0000000000 --- a/src/renderer/components/cluster-manager/catalog-topbar.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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 { welcomeURL } from "../../../common/routes"; -import { navigate } from "../../navigation"; -import { Icon } from "../icon"; -import { TopBar } from "../layout/topbar"; - -export function CatalogTopbar() { - return ( - -
- navigate(welcomeURL())} - tooltip="Close Catalog" - /> -
-
- ); -} diff --git a/src/renderer/components/cluster-manager/cluster-topbar.tsx b/src/renderer/components/cluster-manager/cluster-topbar.tsx deleted file mode 100644 index 20a6120ba1..0000000000 --- a/src/renderer/components/cluster-manager/cluster-topbar.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/** - * 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 { observer } from "mobx-react"; -import React from "react"; - -import { previousActiveTab } from "../+catalog"; -import { ClusterStore } from "../../../common/cluster-store"; -import { catalogURL } from "../../../common/routes"; -import { navigate } from "../../navigation"; -import { Icon } from "../icon"; -import { TopBar } from "../layout/topbar"; - -import type { RouteComponentProps } from "react-router"; -import type { ClusterViewRouteParams } from "../../../common/routes"; -import type { Cluster } from "../../../main/cluster"; -import { TooltipPosition } from "../tooltip"; - -interface Props extends RouteComponentProps { -} - -export const ClusterTopbar = observer((props: Props) => { - const getCluster = (): Cluster | undefined => { - return ClusterStore.getInstance().getById(props.match.params.clusterId); - }; - - return ( - -
- { - navigate(`${catalogURL()}/${previousActiveTab.get()}`); - }} - tooltip={{ - preferredPositions: TooltipPosition.BOTTOM_RIGHT, - children: "Back to Catalog" - }} - /> -
-
- ); -}); diff --git a/src/renderer/components/cluster-manager/cluster-view.tsx b/src/renderer/components/cluster-manager/cluster-view.tsx index 3d0fea8e55..1c8a9435fc 100644 --- a/src/renderer/components/cluster-manager/cluster-view.tsx +++ b/src/renderer/components/cluster-manager/cluster-view.tsx @@ -34,7 +34,7 @@ import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import { navigate } from "../../navigation"; import { catalogURL, ClusterViewRouteParams } from "../../../common/routes"; import { previousActiveTab } from "../+catalog"; -import { ClusterTopbar } from "./cluster-topbar"; +import { TopBar } from "../layout/topbar"; interface Props extends RouteComponentProps { } @@ -105,7 +105,7 @@ export class ClusterView extends React.Component { render() { return (
- + {this.renderStatus()}
); diff --git a/src/renderer/components/layout/__tests__/topbar.test.tsx b/src/renderer/components/layout/__tests__/topbar.test.tsx index a8e69ed302..499a52bc66 100644 --- a/src/renderer/components/layout/__tests__/topbar.test.tsx +++ b/src/renderer/components/layout/__tests__/topbar.test.tsx @@ -20,11 +20,46 @@ */ import React from "react"; -import { render } from "@testing-library/react"; +import { render, fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; import { TopBar } from "../topbar"; import { TopBarRegistry } from "../../../../extensions/registries"; +jest.mock( + "electron", + () => ({ + ipcRenderer: { + on: jest.fn( + (channel: string, listener: (event: any, ...args: any[]) => void) => { + if (channel === "history:can-go-back") { + listener({}, true); + } + + if (channel === "history:can-go-forward") { + listener({}, true); + } + } + ), + }, + }) +); + +const goBack = jest.fn(); +const goForward = jest.fn(); + +jest.mock("@electron/remote", () => { + return { + webContents: { + getFocusedWebContents: () => { + return { + goBack, + goForward + }; + } + } + }; +}); + describe("", () => { beforeEach(() => { TopBarRegistry.createInstance(); @@ -35,15 +70,38 @@ describe("", () => { }); it("renders w/o errors", () => { - const { container } = render(); + const { container } = render(); expect(container).toBeInstanceOf(HTMLElement); }); - it("renders title", async () => { - const { getByTestId } = render(); + it("renders history arrows", async () => { + const { getByTestId } = render(); - expect(await getByTestId("topbarLabel")).toHaveTextContent("topbar"); + expect(await getByTestId("history-back")).toBeInTheDocument(); + expect(await getByTestId("history-forward")).toBeInTheDocument(); + }); + + it("enables arrow by ipc event", async () => { + const { getByTestId } = render(); + + expect(await getByTestId("history-back")).not.toHaveClass("disabled"); + expect(await getByTestId("history-forward")).not.toHaveClass("disabled"); + }); + + it("triggers browser history back and forward", async () => { + const { getByTestId } = render(); + + const prevButton = await getByTestId("history-back"); + const nextButton = await getByTestId("history-forward"); + + fireEvent.click(prevButton); + + expect(goBack).toBeCalled(); + + fireEvent.click(nextButton); + + expect(goForward).toBeCalled(); }); it("renders items", async () => { @@ -58,7 +116,7 @@ describe("", () => { } ]); - const { getByTestId } = render(); + const { getByTestId } = render(); expect(await getByTestId(testId)).toHaveTextContent(text); }); diff --git a/src/renderer/components/layout/sidebar.scss b/src/renderer/components/layout/sidebar.scss index f0d777fadd..18ac932f69 100644 --- a/src/renderer/components/layout/sidebar.scss +++ b/src/renderer/components/layout/sidebar.scss @@ -45,4 +45,12 @@ padding: $padding; text-align: center; } + + .cluster-name { + padding: 1.25rem; + font-weight: bold; + font-size: 1.5rem; + word-break: break-all; + color: var(--textColorAccent); + } } diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index 44e57e9202..adfac26add 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -40,6 +40,8 @@ import { SidebarItem } from "./sidebar-item"; import { Apps } from "../+apps"; import * as routes from "../../../common/routes"; import { Config } from "../+config"; +import { ClusterStore } from "../../../common/cluster-store"; +import { App } from "../app"; interface Props { className?: string; @@ -181,6 +183,9 @@ export class Sidebar extends React.Component { return (
+
+ {ClusterStore.getInstance().getById(App.clusterId)?.name} +
{ - label: React.ReactNode; } -export const TopBar = observer(({ label, children, ...rest }: Props) => { +const prevEnabled = observable.box(false); +const nextEnabled = observable.box(false); + +ipcRendererOn("history:can-go-back", (event, state: boolean) => { + prevEnabled.set(state); +}); + +ipcRendererOn("history:can-go-forward", (event, state: boolean) => { + nextEnabled.set(state); +}); + +export const TopBar = observer(({ children, ...rest }: Props) => { const renderRegisteredItems = () => { const items = TopBarRegistry.getInstance().getItems(); @@ -37,7 +52,7 @@ export const TopBar = observer(({ label, children, ...rest }: Props) => { } return ( -
+
{items.map((registration, index) => { if (!registration?.components?.Item) { return null; @@ -53,9 +68,38 @@ export const TopBar = observer(({ label, children, ...rest }: Props) => { ); }; + const goBack = () => { + webContents.getFocusedWebContents()?.goBack(); + }; + + const goForward = () => { + webContents.getFocusedWebContents()?.goForward(); + }; + + useEffect(() => { + const disposer = watchHistoryState(); + + return () => disposer(); + }, []); + return (
-
{label}
+
+ + +
{renderRegisteredItems()} {children} diff --git a/src/renderer/components/cluster-manager/welcome-topbar.tsx b/src/renderer/remote-helpers/history-updater.ts similarity index 70% rename from src/renderer/components/cluster-manager/welcome-topbar.tsx rename to src/renderer/remote-helpers/history-updater.ts index c528326c7a..3a8c9bfc04 100644 --- a/src/renderer/components/cluster-manager/welcome-topbar.tsx +++ b/src/renderer/remote-helpers/history-updater.ts @@ -19,12 +19,14 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import React from "react"; -import { TopBar } from "../layout/topbar"; +import { webContents } from "@electron/remote"; +import { reaction } from "mobx"; +import { broadcastMessage } from "../../common/ipc"; +import { navigation } from "../navigation"; -export function WelcomeTopbar() { - return ( - - - ); +export function watchHistoryState() { + return reaction(() => navigation.location, () => { + broadcastMessage("history:can-go-back", webContents.getFocusedWebContents()?.canGoBack()); + broadcastMessage("history:can-go-forward", webContents.getFocusedWebContents()?.canGoForward()); + }); }