1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

more fixes & refactoring

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2020-11-12 13:58:05 +02:00
parent 6159ebf9d3
commit a489d9aabe
18 changed files with 109 additions and 102 deletions

View File

@ -100,7 +100,6 @@ import { ExamplePage } from "./src/example-page"
export default class ExampleRendererExtension extends LensRendererExtension {
globalPages = [
{
routePath: "/items/:id?",
components: {
Page: ExamplePage,
}
@ -109,7 +108,6 @@ export default class ExampleRendererExtension extends LensRendererExtension {
globalPageMenus = [
{
url: "/items/1",
title: "Example page", // used in icon's tooltip
components: {
Icon: () => <Component.Icon material="arrow"/>,
@ -155,8 +153,8 @@ import { ExampleIcon, ExamplePage } from "./src/page"
export default class ExampleExtension extends LensRendererExtension {
clusterPages = [
{
routePath: "/extension-example",
exact: true,
routePath: "/extension-example", // optional
exact: true, // optional
components: {
Page: () => <ExamplePage extension={this}/>,
}
@ -165,7 +163,7 @@ export default class ExampleExtension extends LensRendererExtension {
clusterPageMenus = [
{
url: "/extension-example",
url: "/extension-example", // optional
title: "Example Extension",
components: {
Icon: ExampleIcon,
@ -217,11 +215,8 @@ export default class ExampleExtension extends LensRendererExtension {
statusBarItems = [
{
item: (
<div
className="flex align-center gaps hover-highlight"
onClick={() => Navigation.navigate("/example-page")}
>
<Component.Icon material="favorite" smallest />
<div className="flex align-center gaps hover-highlight" onClick={() => this.navigate("/example-page")} >
<Component.Icon material="favorite" />
</div>
)
}

View File

@ -13,7 +13,7 @@ export default class SupportPageRendererExtension extends LensRendererExtension
}
]
statusBarItems = [
statusBarItems: Interface.StatusBarRegistration[] = [
{
item: (
<div className="flex align-center gaps hover-highlight" onClick={() => this.navigate(pageUrl)}>

View File

@ -241,8 +241,6 @@
"openid-client": "^3.15.2",
"path-to-regexp": "^6.1.0",
"proper-lockfile": "^4.1.1",
"react": "^16.14.0",
"react-router": "^5.2.0",
"request": "^2.88.2",
"request-promise-native": "^1.0.8",
"semver": "^7.3.2",
@ -363,9 +361,11 @@
"postinstall-postinstall": "^2.1.0",
"progress-bar-webpack-plugin": "^2.1.0",
"raw-loader": "^4.0.1",
"react": "^16.14.0",
"react-beautiful-dnd": "^13.0.0",
"react-dom": "^16.13.1",
"react-refresh": "^0.9.0",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-select": "^3.1.0",
"react-window": "^1.8.5",

View File

@ -57,29 +57,29 @@ export class ExtensionLoader {
loadOnMain() {
logger.info('[EXTENSIONS-LOADER]: load on main')
this.autoInitExtensions((ext: LensMainExtension) => [
registries.menuRegistry.add(ext.appMenus, { ext })
registries.menuRegistry.add(ext.appMenus, { key: ext })
]);
}
loadOnClusterManagerRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
this.autoInitExtensions((ext: LensRendererExtension) => [
registries.globalPageRegistry.add(ext.globalPages, { ext }),
registries.globalPageMenuRegistry.add(ext.globalPageMenus, { ext }),
registries.appPreferenceRegistry.add(ext.appPreferences, { ext }),
registries.clusterFeatureRegistry.add(ext.clusterFeatures, { ext }),
registries.statusBarRegistry.add(ext.statusBarItems, { ext }),
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 }),
]);
}
loadOnClusterRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
this.autoInitExtensions((ext: LensRendererExtension) => [
registries.clusterPageRegistry.add(ext.clusterPages, { ext }),
registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, { ext }),
registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems, { ext }),
registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems, { ext }),
registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts, { ext })
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 })
])
}

View File

@ -44,11 +44,11 @@ export class LensExtension {
return this.manifest.description
}
getPageUrl(baseUrl: string) {
return compile(this.routePrefix)({ name: this.name }) + baseUrl
getPageUrl(baseUrl = "") {
return compile(this.routePrefix)({ name: this.name }) + baseUrl;
}
getPageRoute(baseRoute: string) {
getPageRoute(baseRoute = "") {
return this.routePrefix + baseRoute;
}

View File

@ -1,11 +1,7 @@
import type {
AppPreferenceRegistration, ClusterFeatureRegistration,
KubeObjectMenuRegistration, KubeObjectDetailRegistration, StatusBarRegistration, KubeObjectStatusRegistration,
PageRegistration, PageMenuRegistration,
} from "./registries"
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries"
import { ipcRenderer } from "electron"
import { observable } from "mobx";
import { LensExtension } from "./lens-extension"
import { ipcRenderer } from "electron"
export class LensRendererExtension extends LensExtension {
@observable.shallow globalPages: PageRegistration[] = []
@ -20,6 +16,6 @@ export class LensRendererExtension extends LensExtension {
@observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = []
navigate(location: string) {
ipcRenderer.emit("renderer:navigate", location)
ipcRenderer.emit("renderer:navigate", this.getPageUrl(location))
}
}

View File

@ -1,12 +1,12 @@
import type React from "react"
import { BaseRegistry } from "./base-registry";
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
export interface AppPreferenceComponents {
Hint: React.ComponentType<any>;
Input: React.ComponentType<any>;
}
export interface AppPreferenceRegistration {
export interface AppPreferenceRegistration extends BaseRegistryItem {
title: string;
components: AppPreferenceComponents;
}

View File

@ -1,16 +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 interface BaseRegistryItem {
id?: BaseRegistryItemId; // uniq id, generated automatically when not provided
}
export interface BaseRegistryAddMeta {
ext?: LensExtension | null;
key?: BaseRegistryKey;
merge?: boolean
}
export class BaseRegistry<T extends object = any> {
private items = observable.map<LensExtension, T[]>([], { deep: false });
export class BaseRegistry<T extends BaseRegistryItem = any> {
private items = observable.map<BaseRegistryKey, T[]>([], { deep: false });
getItems(): (T & { extension?: LensExtension })[] {
getItems(): (T & { extension?: LensExtension | null })[] {
return Array.from(this.items).map(([ext, items]) => {
return items.map(item => ({
...item,
@ -19,29 +27,39 @@ export class BaseRegistry<T extends object = any> {
}).flat()
}
@action
add(items: T | T[], { ext = null, merge = true }: BaseRegistryAddMeta = {}) {
const itemsList: T[] = Array.isArray(items) ? items : [items];
if (merge && this.items.has(ext)) {
const newItems = new Set(this.items.get(ext));
itemsList.forEach(item => newItems.add(item))
this.items.set(ext, [...newItems]);
} else {
this.items.set(ext, itemsList);
getById(itemId: BaseRegistryItemId, key?: BaseRegistryKey): T {
const byId = (item: BaseRegistryItem) => item.id === itemId;
if (key) {
return this.items.get(key)?.find(byId)
}
return () => this.remove(itemsList, ext)
return this.getItems().find(byId);
}
@action
remove(items: T[], key: LensExtension = null) {
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)
}
@action
remove(items: T[], key: BaseRegistryKey = null) {
const storedItems = this.items.get(key);
if (storedItems) {
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);
}
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);
}
}
}

View File

@ -1,17 +1,18 @@
import type React from "react"
import { BaseRegistry } from "./base-registry";
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
import { ClusterFeature } from "../cluster-feature";
export interface ClusterFeatureComponents {
Description: React.ComponentType<any>;
}
export interface ClusterFeatureRegistration {
export interface ClusterFeatureRegistration extends BaseRegistryItem {
title: string;
components: ClusterFeatureComponents
feature: ClusterFeature
}
export class ClusterFeatureRegistry extends BaseRegistry<ClusterFeatureRegistration> {}
export class ClusterFeatureRegistry extends BaseRegistry<ClusterFeatureRegistration> {
}
export const clusterFeatureRegistry = new ClusterFeatureRegistry()

View File

@ -1,11 +1,11 @@
import React from "react"
import { BaseRegistry } from "./base-registry";
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
export interface KubeObjectDetailComponents {
Details: React.ComponentType<any>;
}
export interface KubeObjectDetailRegistration {
export interface KubeObjectDetailRegistration extends BaseRegistryItem {
kind: string;
apiVersions: string[];
components: KubeObjectDetailComponents;

View File

@ -1,11 +1,11 @@
import React from "react"
import { BaseRegistry } from "./base-registry";
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
export interface KubeObjectMenuComponents {
MenuItem: React.ComponentType<any>;
}
export interface KubeObjectMenuRegistration {
export interface KubeObjectMenuRegistration extends BaseRegistryItem {
kind: string;
apiVersions: string[];
components: KubeObjectMenuComponents;

View File

@ -1,7 +1,7 @@
import { KubeObject, KubeObjectStatus } from "../renderer-api/k8s-api";
import { BaseRegistry } from "./base-registry";
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
export interface KubeObjectStatusRegistration {
export interface KubeObjectStatusRegistration extends BaseRegistryItem {
kind: string;
apiVersions: string[];
resolve: (object: KubeObject) => KubeObjectStatus;

View File

@ -2,28 +2,26 @@
import type React from "react";
import type { IconProps } from "../../renderer/components/icon";
import { BaseRegistry } from "./base-registry";
import { matchPath } from "react-router";
import { BaseRegistry, BaseRegistryItem, BaseRegistryItemId } from "./base-registry";
export interface PageMenuRegistration {
url: string;
export interface PageMenuRegistration extends BaseRegistryItem {
id: BaseRegistryItemId; // required id from page-registry item to match with
url?: string; // when not provided initial extension's path used, e.g. "/extension/lens-extension-name"
title: React.ReactNode;
components: PageMenuComponents;
subMenus?: Omit<PageMenuRegistration, "components" | "subMenus">[];
subMenus?: PageSubMenuRegistration[];
}
export interface PageSubMenuRegistration {
url: string;
title: React.ReactNode;
}
export interface PageMenuComponents {
Icon: React.ComponentType<IconProps>;
}
export class PageMenuRegistry extends BaseRegistry<PageMenuRegistration> {
getByMatchingRoute(routePath: string | string[], exact?: boolean) {
return this.getItems().find(item => !!matchPath(item.url, {
path: routePath,
exact,
}))
}
export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> {
getItems() {
return super.getItems().map(item => {
item.url = item.extension.getPageUrl(item.url)

View File

@ -1,14 +1,19 @@
// Extensions-api -> Custom page registration
import React from "react";
import { matchPath } from "react-router";
import { BaseRegistry } from "./base-registry";
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
export interface PageRegistration {
routePath: string; // react-router's path, e.g. "/page/:id"
export interface PageRegistration extends BaseRegistryItem {
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;
subPages?: Omit<PageRegistration, "subPages">[];
subPages?: SubPageRegistration[];
}
export interface SubPageRegistration {
routePath: string; // required for sub-pages
exact?: boolean;
components: PageComponents;
}
export interface PageComponents {
@ -16,12 +21,6 @@ export interface PageComponents {
}
export class PageRegistry extends BaseRegistry<PageRegistration> {
getByMatchingUrl(baseUrl: string) {
return this.getItems().find(({ routePath: path, exact }) => {
return !!matchPath(baseUrl, { path, exact });
})
}
getItems() {
return super.getItems().map(item => {
item.routePath = item.extension.getPageRoute(item.routePath)

View File

@ -1,9 +1,9 @@
// Extensions API -> Status bar customizations
import React from "react";
import { BaseRegistry } from "./base-registry";
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
export interface StatusBarRegistration {
export interface StatusBarRegistration extends BaseRegistryItem {
item?: React.ReactNode;
}

View File

@ -74,17 +74,17 @@ export class App extends React.Component {
}
renderExtensionRoutes() {
return clusterPageRegistry.getItems().map(({ components: { Page }, exact, routePath, subPages }) => {
return clusterPageRegistry.getItems().map(({ id: pageId, components: { Page }, exact, routePath, subPages }) => {
const Component = () => {
if (subPages) {
const tabs: TabLayoutRoute[] = subPages.map(({ exact, routePath, components: { Page } }) => {
const matchingUrl = clusterPageMenuRegistry.getByMatchingRoute(routePath, exact)
if (!matchingUrl) return;
const menuItem = clusterPageMenuRegistry.getById(pageId);
if (!menuItem) return;
return {
routePath, exact,
component: Page,
url: matchingUrl.url,
title: matchingUrl.title,
url: menuItem.url,
title: menuItem.title,
}
}).filter(Boolean);
if (tabs.length > 0) {

View File

@ -149,8 +149,8 @@ export class ClustersMenu extends React.Component<Props> {
)}
</div>
<div className="extensions">
{globalPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => {
const registeredPage = globalPageRegistry.getByMatchingUrl(url);
{globalPageMenuRegistry.getItems().map(({ id: menuItemId, title, url, components: { Icon } }) => {
const registeredPage = globalPageRegistry.getById(menuItemId);
if (!registeredPage) return;
const { routePath, exact } = registeredPage;
const isActive = !!matchPath(navigation.location.pathname, { path: routePath, exact });

View File

@ -180,8 +180,8 @@ export class Sidebar extends React.Component<Props> {
>
{this.renderCustomResources()}
</SidebarNavItem>
{clusterPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => {
const registeredPage = clusterPageRegistry.getByMatchingUrl(url);
{clusterPageMenuRegistry.getItems().map(({ id: menuItemId, title, url, components: { Icon } }) => {
const registeredPage = clusterPageRegistry.getById(menuItemId);
if (!registeredPage) return;
const { routePath, exact } = registeredPage;
return (