From 0561f6bfd0417ea06fd99121d325d0e883589168 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 17 Mar 2021 13:57:49 -0400 Subject: [PATCH] Add common app routes to the protocol renderer router (#2272) --- docs/clusters/adding-clusters.md | 2 +- docs/extensions/README.md | 5 +- docs/getting-started/README.md | 1 + docs/getting-started/preferences.md | 10 ++-- extensions/telemetry/renderer.tsx | 1 + src/common/protocol-handler/router.ts | 4 +- src/common/utils/buildUrl.ts | 10 +++- .../registries/app-preference-registry.ts | 13 ++++- .../components/+preferences/preferences.tsx | 24 ++++++-- src/renderer/lens-app.tsx | 3 +- src/renderer/navigation/helpers.ts | 4 +- src/renderer/navigation/history.ts | 2 +- src/renderer/navigation/index.ts | 2 - src/renderer/navigation/protocol-handlers.ts | 10 ---- src/renderer/protocol-handler/app-handlers.ts | 58 +++++++++++++++++++ src/renderer/protocol-handler/index.ts | 3 +- webpack.extensions.ts | 10 ++++ 17 files changed, 129 insertions(+), 33 deletions(-) delete mode 100644 src/renderer/navigation/protocol-handlers.ts create mode 100644 src/renderer/protocol-handler/app-handlers.ts diff --git a/docs/clusters/adding-clusters.md b/docs/clusters/adding-clusters.md index d153d8c9bf..c9a533a700 100644 --- a/docs/clusters/adding-clusters.md +++ b/docs/clusters/adding-clusters.md @@ -2,7 +2,7 @@ Add clusters by clicking the **Add Cluster** button in the left-side menu. -1. Click the **Add Cluster** button (indicated with a '+' icon). +1. Click the **Add Cluster** button (indicated with a '+' icon). Or [click here](lens://app/cluster). 2. Enter the path to your kubeconfig file. You'll need to have a kubeconfig file for the cluster you want to add. You can either browse for the path from the file system or or enter it directly. Selected [cluster contexts](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#context) are added as a separate item in the left-side cluster menu to allow you to operate easily on multiple clusters and/or contexts. diff --git a/docs/extensions/README.md b/docs/extensions/README.md index 7866b65497..f8906d75ee 100644 --- a/docs/extensions/README.md +++ b/docs/extensions/README.md @@ -1,6 +1,9 @@ # Lens Extension API -Customize and enhance the Lens experience with the Lens Extension API. Use the extension API to create menus or page content. The same extension API was used to create many of Lens's core features. +Customize and enhance the Lens experience with the Lens Extension API. +Use the extension API to create menus or page content. +The same extension API was used to create many of Lens's core features. +To install your first extension you should goto the [extension page](lens://app/extensions) in lens. This documentation describes: diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md index ed6537bd76..8f5811c1be 100644 --- a/docs/getting-started/README.md +++ b/docs/getting-started/README.md @@ -72,5 +72,6 @@ To stay current with the Lens features, you can review the [release notes](https ## Next Steps +- [Launch Lens](lens://app/landing) - [Add clusters](../clusters/adding-clusters.md) - [Watch introductory videos](./introductory-videos.md) diff --git a/docs/getting-started/preferences.md b/docs/getting-started/preferences.md index 527f5e6df5..8146965cc5 100644 --- a/docs/getting-started/preferences.md +++ b/docs/getting-started/preferences.md @@ -1,12 +1,11 @@ # Preferences - ## Color Themes The Color Themes option in Lens preferences lets you set the colors in the Lens user interface to suit your liking. -1. Go to **File** > **Preferences** (**Lens** > **Preferences** on Mac). -2. Select your preferred theme from the **Color Theme** dropdown. +1. Go to **File** > **Preferences** (**Lens** > **Preferences** on Mac). Or follow [this link](lens://app/preferences?highlight=appearance). +2. Select your preferred theme from the **Color Theme** dropdown. ![Color Theme](images/color-theme.png) @@ -19,10 +18,9 @@ Lens collects telemetry data, which is used to help us understand how to improve If you don't wish to send usage data to Mirantis, you can disable the "Telemetry & Usage Tracking" in the Lens preferences. -1. Go to **File** > **Preferences** (**Lens** > **Preferences** on Mac). +1. Go to **File** > **Preferences** (**Lens** > **Preferences** on Mac). Or follow [this link](lens://app/preferences?highlight=telemetry-tracking). 2. Scroll down to **Telemetry & Usage Tracking** -3. Uncheck **Allow Telemetry & Usage Tracking**. +3. Uncheck **Allow Telemetry & Usage Tracking**. This will silence all telemetry events from Lens going forward. Telemetry information may have been collected and sent up until the point when you disable this setting. ![Disable Telemetry & Usage Tracking](images/disabled-telemetry-usage-tracking.png) - diff --git a/extensions/telemetry/renderer.tsx b/extensions/telemetry/renderer.tsx index 1078d18f4c..d8ba4225df 100644 --- a/extensions/telemetry/renderer.tsx +++ b/extensions/telemetry/renderer.tsx @@ -8,6 +8,7 @@ export default class TelemetryRendererExtension extends LensRendererExtension { appPreferences = [ { title: "Telemetry & Usage Tracking", + id: "telemetry-tracking", components: { Hint: () => , Input: () => diff --git a/src/common/protocol-handler/router.ts b/src/common/protocol-handler/router.ts index b18eb84368..7b7659992f 100644 --- a/src/common/protocol-handler/router.ts +++ b/src/common/protocol-handler/router.ts @@ -184,10 +184,12 @@ export abstract class LensProtocolRouter extends Singleton { * @param pathSchema the URI path schema to match against for this handler * @param handler a function that will be called if a protocol path matches */ - public addInternalHandler(urlSchema: string, handler: RouteHandler): void { + public addInternalHandler(urlSchema: string, handler: RouteHandler): this { pathToRegexp(urlSchema); // verify now that the schema is valid logger.info(`${LensProtocolRouter.LoggingPrefix}: internal registering ${urlSchema}`); this.internalRoutes.set(urlSchema, handler); + + return this; } /** diff --git a/src/common/utils/buildUrl.ts b/src/common/utils/buildUrl.ts index e3bad9b302..ba2b31d2d0 100644 --- a/src/common/utils/buildUrl.ts +++ b/src/common/utils/buildUrl.ts @@ -3,14 +3,20 @@ import { compile } from "path-to-regexp"; export interface IURLParams

{ params?: P; query?: Q; + fragment?: string; } export function buildURL

(path: string | any) { const pathBuilder = compile(String(path)); - return function ({ params, query }: IURLParams = {}) { + return function ({ params, query, fragment }: IURLParams = {}): string { const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : ""; + const parts = [ + pathBuilder(params), + queryParams && `?${queryParams}`, + fragment && `#${fragment}`, + ]; - return pathBuilder(params) + (queryParams ? `?${queryParams}` : ""); + return parts.filter(Boolean).join(""); }; } diff --git a/src/extensions/registries/app-preference-registry.ts b/src/extensions/registries/app-preference-registry.ts index 338f93b5bc..e6b5d93cb2 100644 --- a/src/extensions/registries/app-preference-registry.ts +++ b/src/extensions/registries/app-preference-registry.ts @@ -8,10 +8,21 @@ export interface AppPreferenceComponents { export interface AppPreferenceRegistration { title: string; + id?: string; components: AppPreferenceComponents; } -export class AppPreferenceRegistry extends BaseRegistry { +export interface RegisteredAppPreference extends AppPreferenceRegistration { + id: string; +} + +export class AppPreferenceRegistry extends BaseRegistry { + getRegisteredItem(item: AppPreferenceRegistration): RegisteredAppPreference { + return { + id: item.id || item.title.toLowerCase().replace(/[^0-9a-zA-Z]+/g, "-"), + ...item, + }; + } } export const appPreferenceRegistry = new AppPreferenceRegistry(); diff --git a/src/renderer/components/+preferences/preferences.tsx b/src/renderer/components/+preferences/preferences.tsx index 1681ebf596..eada7eb41e 100644 --- a/src/renderer/components/+preferences/preferences.tsx +++ b/src/renderer/components/+preferences/preferences.tsx @@ -1,8 +1,8 @@ import "./preferences.scss"; import React from "react"; -import { computed, observable } from "mobx"; -import { observer } from "mobx-react"; +import { computed, observable, reaction } from "mobx"; +import { disposeOnUnmount, observer } from "mobx-react"; import { userStore } from "../../../common/user-store"; import { isWindows } from "../../../common/vars"; @@ -16,6 +16,7 @@ import { Select, SelectOption } from "../select"; import { HelmCharts } from "./helm-charts"; import { KubectlBinaries } from "./kubectl-binaries"; import { ScrollSpy } from "../scroll-spy/scroll-spy"; +import { navigation } from "../../navigation"; @observer export class Preferences extends React.Component { @@ -29,6 +30,21 @@ export class Preferences extends React.Component { })); } + componentDidMount() { + disposeOnUnmount(this, [ + reaction(() => navigation.location.hash, hash => { + const fragment = hash.slice(1); // hash is /^(#\w.)?$/ + + if (fragment) { + // ignore empty framents + document.getElementById(fragment)?.scrollIntoView(); + } + }, { + fireImmediately: true + }) + ]); + } + render() { const { preferences } = userStore; const header =

Preferences

; @@ -133,10 +149,10 @@ export class Preferences extends React.Component {

Extensions

- {appPreferenceRegistry.getItems().map(({ title, components: { Hint, Input } }, index) => { + {appPreferenceRegistry.getItems().map(({ title, id, components: { Hint, Input } }, index) => { return (
-

{title}

+

{title}

diff --git a/src/renderer/lens-app.tsx b/src/renderer/lens-app.tsx index c6f55872a9..f5384b77f1 100644 --- a/src/renderer/lens-app.tsx +++ b/src/renderer/lens-app.tsx @@ -12,7 +12,7 @@ import { ConfirmDialog } from "./components/confirm-dialog"; import { extensionLoader } from "../extensions/extension-loader"; import { broadcastMessage } from "../common/ipc"; import { CommandContainer } from "./components/command-palette/command-container"; -import { LensProtocolRouterRenderer } from "./protocol-handler/router"; +import { LensProtocolRouterRenderer, bindProtocolAddRouteHandlers } from "./protocol-handler"; import { registerIpcHandlers } from "./ipc"; import { ipcRenderer } from "electron"; @@ -21,6 +21,7 @@ export class LensApp extends React.Component { static async init() { extensionLoader.loadOnClusterManagerRenderer(); LensProtocolRouterRenderer.getInstance().init(); + bindProtocolAddRouteHandlers(); window.addEventListener("offline", () => { broadcastMessage("network:offline"); }); diff --git a/src/renderer/navigation/helpers.ts b/src/renderer/navigation/helpers.ts index 0eda77c629..399eee6325 100644 --- a/src/renderer/navigation/helpers.ts +++ b/src/renderer/navigation/helpers.ts @@ -5,11 +5,11 @@ import { clusterViewRoute, IClusterViewRouteParams } from "../components/cluster import { navigation } from "./history"; export function navigate(location: LocationDescriptor) { - const currentLocation = navigation.getPath(); + const currentLocation = navigation.location.pathname; navigation.push(location); - if (currentLocation === navigation.getPath()) { + if (currentLocation === navigation.location.pathname) { navigation.goBack(); // prevent sequences of same url in history } } diff --git a/src/renderer/navigation/history.ts b/src/renderer/navigation/history.ts index f12b89f365..c1ce34625b 100644 --- a/src/renderer/navigation/history.ts +++ b/src/renderer/navigation/history.ts @@ -10,5 +10,5 @@ navigation.listen((location, action) => { const isClusterView = !process.isMainFrame; const domain = global.location.href; - logger.debug(`[NAVIGATION]: ${action}`, { isClusterView, domain, location }); + logger.debug(`[NAVIGATION]: ${action}-ing. Current is now:`, { isClusterView, domain, location }); }); diff --git a/src/renderer/navigation/index.ts b/src/renderer/navigation/index.ts index adf2577f4e..94930fc994 100644 --- a/src/renderer/navigation/index.ts +++ b/src/renderer/navigation/index.ts @@ -1,10 +1,8 @@ // Navigation (renderer) import { bindEvents } from "./events"; -import { bindProtocolHandlers } from "./protocol-handlers"; export * from "./history"; export * from "./helpers"; bindEvents(); -bindProtocolHandlers(); diff --git a/src/renderer/navigation/protocol-handlers.ts b/src/renderer/navigation/protocol-handlers.ts deleted file mode 100644 index 423cc70fd0..0000000000 --- a/src/renderer/navigation/protocol-handlers.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { LensProtocolRouterRenderer } from "../protocol-handler/router"; -import { navigate } from "./helpers"; - -export function bindProtocolHandlers() { - const lprr = LensProtocolRouterRenderer.getInstance(); - - lprr.addInternalHandler("/preferences", () => { - navigate("/preferences"); - }); -} diff --git a/src/renderer/protocol-handler/app-handlers.ts b/src/renderer/protocol-handler/app-handlers.ts new file mode 100644 index 0000000000..256b8b396a --- /dev/null +++ b/src/renderer/protocol-handler/app-handlers.ts @@ -0,0 +1,58 @@ +import { addClusterURL } from "../components/+add-cluster"; +import { clusterSettingsURL } from "../components/+cluster-settings"; +import { extensionsURL } from "../components/+extensions"; +import { landingURL } from "../components/+landing-page"; +import { preferencesURL } from "../components/+preferences"; +import { clusterViewURL } from "../components/cluster-manager/cluster-view.route"; +import { LensProtocolRouterRenderer } from "./router"; +import { navigate } from "../navigation/helpers"; +import { clusterStore } from "../../common/cluster-store"; +import { workspaceStore } from "../../common/workspace-store"; + +export function bindProtocolAddRouteHandlers() { + LensProtocolRouterRenderer + .getInstance() + .addInternalHandler("/preferences", ({ search: { highlight }}) => { + navigate(preferencesURL({ fragment: highlight })); + }) + .addInternalHandler("/", () => { + navigate(landingURL()); + }) + .addInternalHandler("/landing", () => { + navigate(landingURL()); + }) + .addInternalHandler("/landing/:workspaceId", ({ pathname: { workspaceId } }) => { + if (workspaceStore.getById(workspaceId)) { + workspaceStore.setActive(workspaceId); + navigate(landingURL()); + } else { + console.log("[APP-HANDLER]: workspace with given ID does not exist", { workspaceId }); + } + }) + .addInternalHandler("/cluster", () => { + navigate(addClusterURL()); + }) + .addInternalHandler("/cluster/:clusterId", ({ pathname: { clusterId } }) => { + const cluster = clusterStore.getById(clusterId); + + if (cluster) { + workspaceStore.setActive(cluster.workspace); + navigate(clusterViewURL({ params: { clusterId } })); + } else { + console.log("[APP-HANDLER]: cluster with given ID does not exist", { clusterId }); + } + }) + .addInternalHandler("/cluster/:clusterId/settings", ({ pathname: { clusterId } }) => { + const cluster = clusterStore.getById(clusterId); + + if (cluster) { + workspaceStore.setActive(cluster.workspace); + navigate(clusterSettingsURL({ params: { clusterId } })); + } else { + console.log("[APP-HANDLER]: cluster with given ID does not exist", { clusterId }); + } + }) + .addInternalHandler("/extensions", () => { + navigate(extensionsURL()); + }); +} diff --git a/src/renderer/protocol-handler/index.ts b/src/renderer/protocol-handler/index.ts index d18015da88..5b2f784711 100644 --- a/src/renderer/protocol-handler/index.ts +++ b/src/renderer/protocol-handler/index.ts @@ -1 +1,2 @@ -export * from "./router.ts"; +export * from "./router"; +export * from "./app-handlers"; diff --git a/webpack.extensions.ts b/webpack.extensions.ts index 1b9c812868..24e8aeac0c 100644 --- a/webpack.extensions.ts +++ b/webpack.extensions.ts @@ -53,6 +53,16 @@ export default function generateExtensionTypes(): webpack.Configuration { } } }, + { + test: /\.(jpg|png|svg|map|ico)$/, + use: { + loader: "file-loader", + options: { + name: "images/[name]-[hash:6].[ext]", + esModule: false, // handle media imports in