From 6d0c07b3d1633e812497fa9bc848b8d9caf0b8ea Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Fri, 13 Nov 2020 09:27:48 +0200 Subject: [PATCH] simplify page/menu/registry implementation Signed-off-by: Jari Kolehmainen --- src/extensions/extension-loader.ts | 22 +++---- .../registries/app-preference-registry.ts | 4 +- src/extensions/registries/base-registry.ts | 65 ++++--------------- .../registries/cluster-feature-registry.ts | 4 +- .../registries/kube-object-detail-registry.ts | 4 +- .../registries/kube-object-menu-registry.ts | 4 +- .../registries/kube-object-status-registry.ts | 4 +- .../registries/page-menu-registry.ts | 24 ++++--- src/extensions/registries/page-registry.ts | 31 +++++++-- .../registries/status-bar-registry.ts | 4 +- src/renderer/components/app.tsx | 4 +- .../cluster-manager/cluster-manager.tsx | 2 +- .../cluster-manager/clusters-menu.tsx | 4 +- src/renderer/components/layout/sidebar.tsx | 5 +- 14 files changed, 83 insertions(+), 98 deletions(-) diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts index b75fd5147d..969cff33b1 100644 --- a/src/extensions/extension-loader.ts +++ b/src/extensions/extension-loader.ts @@ -57,29 +57,29 @@ export class ExtensionLoader { loadOnMain() { logger.info('[EXTENSIONS-LOADER]: load on main') this.autoInitExtensions((ext: LensMainExtension) => [ - registries.menuRegistry.add(ext.appMenus, { key: ext }) + registries.menuRegistry.add(ext.appMenus) ]); } loadOnClusterManagerRenderer() { logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)') this.autoInitExtensions((ext: LensRendererExtension) => [ - registries.globalPageRegistry.add(ext.globalPages, { key: ext }), - registries.globalPageMenuRegistry.add(ext.globalPageMenus, { key: ext }), - registries.appPreferenceRegistry.add(ext.appPreferences, { key: ext }), - registries.clusterFeatureRegistry.add(ext.clusterFeatures, { key: ext }), - registries.statusBarRegistry.add(ext.statusBarItems, { key: ext }), + registries.globalPageRegistry.add(ext.globalPages, ext), + registries.globalPageMenuRegistry.add(ext.globalPageMenus, ext), + registries.appPreferenceRegistry.add(ext.appPreferences), + registries.clusterFeatureRegistry.add(ext.clusterFeatures), + registries.statusBarRegistry.add(ext.statusBarItems), ]); } loadOnClusterRenderer() { logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)') this.autoInitExtensions((ext: LensRendererExtension) => [ - registries.clusterPageRegistry.add(ext.clusterPages, { key: ext }), - registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, { key: ext }), - registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems, { key: ext }), - registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems, { key: ext }), - registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts, { key: ext }) + registries.clusterPageRegistry.add(ext.clusterPages, ext), + registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, ext), + registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems), + registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems), + registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts) ]) } diff --git a/src/extensions/registries/app-preference-registry.ts b/src/extensions/registries/app-preference-registry.ts index dc15ec3e20..6c54911f82 100644 --- a/src/extensions/registries/app-preference-registry.ts +++ b/src/extensions/registries/app-preference-registry.ts @@ -1,12 +1,12 @@ import type React from "react" -import { BaseRegistry, BaseRegistryItem } from "./base-registry"; +import { BaseRegistry } from "./base-registry"; export interface AppPreferenceComponents { Hint: React.ComponentType; Input: React.ComponentType; } -export interface AppPreferenceRegistration extends BaseRegistryItem { +export interface AppPreferenceRegistration { title: string; components: AppPreferenceComponents; } diff --git a/src/extensions/registries/base-registry.ts b/src/extensions/registries/base-registry.ts index 76678791f9..7cd2d98223 100644 --- a/src/extensions/registries/base-registry.ts +++ b/src/extensions/registries/base-registry.ts @@ -1,65 +1,24 @@ // Base class for extensions-api registries import { action, observable } from "mobx"; -import { LensExtension } from "../lens-extension"; -import { getRandId } from "../../common/utils"; -export type BaseRegistryKey = LensExtension | null; -export type BaseRegistryItemId = string | symbol; +export class BaseRegistry { + protected items = observable([], { deep: false }); -export interface BaseRegistryItem { - id?: BaseRegistryItemId; // uniq id, generated automatically when not provided -} - -export interface BaseRegistryAddMeta { - key?: BaseRegistryKey; - merge?: boolean -} - -export class BaseRegistry { - private items = observable.map([], { deep: false }); - - getItems(): (T & { extension?: LensExtension | null })[] { - return Array.from(this.items).map(([ext, items]) => { - return items.map(item => ({ - ...item, - extension: ext, - })) - }).flat() - } - - getById(itemId: BaseRegistryItemId, key?: BaseRegistryKey): T { - const byId = (item: BaseRegistryItem) => item.id === itemId; - if (key) { - return this.items.get(key)?.find(byId) - } - return this.getItems().find(byId); + getItems(): T[] { + return this.items.toJS(); } @action - add(items: T | T[], { key = null, merge = true }: BaseRegistryAddMeta = {}) { - const normalizedItems = (Array.isArray(items) ? items : [items]).map((item: T) => { - item.id = item.id || getRandId(); - return item; - }); - if (merge && this.items.has(key)) { - const newItems = new Set(this.items.get(key)); - normalizedItems.forEach(item => newItems.add(item)) - this.items.set(key, [...newItems]); - } else { - this.items.set(key, normalizedItems); - } - return () => this.remove(normalizedItems, key) + add(items: T | T[]) { + const normalizedItems = (Array.isArray(items) ? items : [items]) + this.items.push(...normalizedItems); + return () => this.remove(...normalizedItems); } @action - remove(items: T[], key: BaseRegistryKey = null) { - const storedItems = this.items.get(key); - if (!storedItems) return; - const newItems = storedItems.filter(item => !items.includes(item)); // works because of {deep: false}; - if (newItems.length > 0) { - this.items.set(key, newItems) - } else { - this.items.delete(key); - } + remove(...items: T[]) { + items.forEach(item => { + this.items.remove(item); // works because of {deep: false}; + }) } } diff --git a/src/extensions/registries/cluster-feature-registry.ts b/src/extensions/registries/cluster-feature-registry.ts index 17a4ef1eef..0e3363d0f0 100644 --- a/src/extensions/registries/cluster-feature-registry.ts +++ b/src/extensions/registries/cluster-feature-registry.ts @@ -1,12 +1,12 @@ import type React from "react" -import { BaseRegistry, BaseRegistryItem } from "./base-registry"; +import { BaseRegistry } from "./base-registry"; import { ClusterFeature } from "../cluster-feature"; export interface ClusterFeatureComponents { Description: React.ComponentType; } -export interface ClusterFeatureRegistration extends BaseRegistryItem { +export interface ClusterFeatureRegistration { title: string; components: ClusterFeatureComponents feature: ClusterFeature diff --git a/src/extensions/registries/kube-object-detail-registry.ts b/src/extensions/registries/kube-object-detail-registry.ts index 63d9cfa30d..32fea66b81 100644 --- a/src/extensions/registries/kube-object-detail-registry.ts +++ b/src/extensions/registries/kube-object-detail-registry.ts @@ -1,11 +1,11 @@ import React from "react" -import { BaseRegistry, BaseRegistryItem } from "./base-registry"; +import { BaseRegistry } from "./base-registry"; export interface KubeObjectDetailComponents { Details: React.ComponentType; } -export interface KubeObjectDetailRegistration extends BaseRegistryItem { +export interface KubeObjectDetailRegistration { kind: string; apiVersions: string[]; components: KubeObjectDetailComponents; diff --git a/src/extensions/registries/kube-object-menu-registry.ts b/src/extensions/registries/kube-object-menu-registry.ts index 3e0cfb0251..8f527d6a3d 100644 --- a/src/extensions/registries/kube-object-menu-registry.ts +++ b/src/extensions/registries/kube-object-menu-registry.ts @@ -1,11 +1,11 @@ import React from "react" -import { BaseRegistry, BaseRegistryItem } from "./base-registry"; +import { BaseRegistry } from "./base-registry"; export interface KubeObjectMenuComponents { MenuItem: React.ComponentType; } -export interface KubeObjectMenuRegistration extends BaseRegistryItem { +export interface KubeObjectMenuRegistration { kind: string; apiVersions: string[]; components: KubeObjectMenuComponents; diff --git a/src/extensions/registries/kube-object-status-registry.ts b/src/extensions/registries/kube-object-status-registry.ts index 3a516a82e1..74fd8145d2 100644 --- a/src/extensions/registries/kube-object-status-registry.ts +++ b/src/extensions/registries/kube-object-status-registry.ts @@ -1,7 +1,7 @@ import { KubeObject, KubeObjectStatus } from "../renderer-api/k8s-api"; -import { BaseRegistry, BaseRegistryItem } from "./base-registry"; +import { BaseRegistry } from "./base-registry"; -export interface KubeObjectStatusRegistration extends BaseRegistryItem { +export interface KubeObjectStatusRegistration { kind: string; apiVersions: string[]; resolve: (object: KubeObject) => KubeObjectStatus; diff --git a/src/extensions/registries/page-menu-registry.ts b/src/extensions/registries/page-menu-registry.ts index 6bd161ccb2..0748fad187 100644 --- a/src/extensions/registries/page-menu-registry.ts +++ b/src/extensions/registries/page-menu-registry.ts @@ -1,11 +1,13 @@ // Extensions-api -> Register page menu items import type React from "react"; +import { action } from "mobx"; import type { IconProps } from "../../renderer/components/icon"; -import { BaseRegistry, BaseRegistryItem, BaseRegistryItemId } from "./base-registry"; +import { BaseRegistry } from "./base-registry"; +import { LensExtension } from "../lens-extension"; +import { getPageUrl } from "./page-registry"; -export interface PageMenuRegistration extends BaseRegistryItem { - id: BaseRegistryItemId; // required id from page-registry item to match with +export interface PageMenuRegistration { url?: string; // when not provided initial extension's path used, e.g. "/extension/lens-extension-name" title: React.ReactNode; components: PageMenuComponents; @@ -22,11 +24,17 @@ export interface PageMenuComponents { } export class PageMenuRegistry extends BaseRegistry { - getItems() { - return super.getItems().map(item => { - item.url = item.extension.getPageUrl(item.url) - return item - }); + @action + add(items: T[], ext?: LensExtension) { + const normalizedItems = items.map((i) => { + i.url = getPageUrl(ext, i.url) + return i + }) + return super.add(normalizedItems); + } + + getByRoutePath(routePath: string) { + return this.getItems().find((i) => i.url === routePath) } } diff --git a/src/extensions/registries/page-registry.ts b/src/extensions/registries/page-registry.ts index 886ab8a584..2511f28a95 100644 --- a/src/extensions/registries/page-registry.ts +++ b/src/extensions/registries/page-registry.ts @@ -1,9 +1,12 @@ // Extensions-api -> Custom page registration import React from "react"; -import { BaseRegistry, BaseRegistryItem } from "./base-registry"; +import { action } from "mobx"; +import { compile } from "path-to-regexp"; +import { BaseRegistry } from "./base-registry"; +import { LensExtension } from "../lens-extension" -export interface PageRegistration extends BaseRegistryItem { +export interface PageRegistration { routePath?: string; // additional (suffix) route path to base extension's route: "/extension/:name" exact?: boolean; // route matching flag, see: https://reactrouter.com/web/api/NavLink/exact-bool components: PageComponents; @@ -20,12 +23,26 @@ export interface PageComponents { Page: React.ComponentType; } +const routePrefix = "/extension/:name" + +export function getPageUrl(ext: LensExtension, baseUrl = "") { + const validUrlName = ext.name.replace("@", "").replace("/", "-"); + return compile(routePrefix)({ name: validUrlName }) + baseUrl; +} + export class PageRegistry extends BaseRegistry { - getItems() { - return super.getItems().map(item => { - item.routePath = item.extension.getPageRoute(item.routePath) - return item - }); + + @action + add(items: T[], ext?: LensExtension) { + const normalizedItems = items.map((i) => { + i.routePath = getPageUrl(ext, i.routePath) + return i + }) + return super.add(normalizedItems); + } + + getByUrl(url: string) { + return this.getItems().find((i) => i.routePath === url) } } diff --git a/src/extensions/registries/status-bar-registry.ts b/src/extensions/registries/status-bar-registry.ts index c1029e6d68..88c4132d30 100644 --- a/src/extensions/registries/status-bar-registry.ts +++ b/src/extensions/registries/status-bar-registry.ts @@ -1,9 +1,9 @@ // Extensions API -> Status bar customizations import React from "react"; -import { BaseRegistry, BaseRegistryItem } from "./base-registry"; +import { BaseRegistry } from "./base-registry"; -export interface StatusBarRegistration extends BaseRegistryItem { +export interface StatusBarRegistration { item?: React.ReactNode; } diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 45b7d565f2..193a1a8796 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -74,11 +74,11 @@ export class App extends React.Component { } renderExtensionRoutes() { - return clusterPageRegistry.getItems().map(({ id: pageId, components: { Page }, exact, routePath, subPages }) => { + return clusterPageRegistry.getItems().map(({ components: { Page }, exact, routePath, subPages }) => { const Component = () => { if (subPages) { const tabs: TabLayoutRoute[] = subPages.map(({ exact, routePath, components: { Page } }) => { - const menuItem = clusterPageMenuRegistry.getById(pageId); + const menuItem = clusterPageMenuRegistry.getByRoutePath(routePath); if (!menuItem) return; return { routePath, exact, diff --git a/src/renderer/components/cluster-manager/cluster-manager.tsx b/src/renderer/components/cluster-manager/cluster-manager.tsx index 1522376c3e..a6dabcba10 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.tsx +++ b/src/renderer/components/cluster-manager/cluster-manager.tsx @@ -70,7 +70,7 @@ export class ClusterManager extends React.Component { {globalPageRegistry.getItems().map(({ routePath, exact, components: { Page } }) => { - return + return })} diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx index bc51799934..4c81e967b6 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.tsx +++ b/src/renderer/components/cluster-manager/clusters-menu.tsx @@ -148,8 +148,8 @@ export class ClustersMenu extends React.Component { )}
- {globalPageMenuRegistry.getItems().map(({ id: menuItemId, title, url, components: { Icon } }) => { - const registeredPage = globalPageRegistry.getById(menuItemId); + {globalPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => { + const registeredPage = globalPageRegistry.getByUrl(url); if (!registeredPage) return; const { routePath, exact } = registeredPage; return ( diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index b70d7a3e62..13f47ce8b5 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -191,8 +191,9 @@ export class Sidebar extends React.Component { > {this.renderCustomResources()} - {clusterPageMenuRegistry.getItems().map(({ id: menuItemId, title, url, components: { Icon } }) => { - const registeredPage = clusterPageRegistry.getById(menuItemId); + {clusterPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => { + const registeredPage = clusterPageRegistry.getByUrl(url); + console.log(url, registeredPage) if (!registeredPage) return; const { routePath, exact } = registeredPage; return (