mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Simplify pages/menus/registry extension api internal implementation (#1364)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> Co-authored-by: Roman <ixrock@gmail.com>
This commit is contained in:
1 parent
eccc502ef2
commit
df0f080380
21 files changed
+176
-142
No files matched your search
@@ -0,0 +1,31 @@
|
||||
import { getPageUrl } from "../page-registry"
|
||||
import { LensExtension } from "../../lens-extension"
|
||||
|
||||
let ext: LensExtension = null
|
||||
|
||||
describe("getPageUrl", () => {
|
||||
beforeEach(async () => {
|
||||
ext = new LensExtension({
|
||||
manifest: {
|
||||
name: "foo-bar",
|
||||
version: "0.1.1"
|
||||
},
|
||||
manifestPath: "/this/is/fake/package.json",
|
||||
isBundled: false,
|
||||
isEnabled: true
|
||||
})
|
||||
})
|
||||
|
||||
it("returns a page url for extension", () => {
|
||||
expect(getPageUrl(ext)).toBe("/extension/foo-bar")
|
||||
})
|
||||
|
||||
it("allows to pass base url as parameter", () => {
|
||||
expect(getPageUrl(ext, "/test")).toBe("/extension/foo-bar/test")
|
||||
})
|
||||
|
||||
it("removes @", () => {
|
||||
ext.manifest.name = "@foo/bar"
|
||||
expect(getPageUrl(ext)).toBe("/extension/foo-bar")
|
||||
})
|
||||
})
|
||||
@@ -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<any>;
|
||||
Input: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
export interface AppPreferenceRegistration extends BaseRegistryItem {
|
||||
export interface AppPreferenceRegistration {
|
||||
title: string;
|
||||
components: AppPreferenceComponents;
|
||||
}
|
||||
|
||||
@@ -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<T = any> {
|
||||
private items = observable<T>([], { deep: false });
|
||||
|
||||
export interface BaseRegistryItem {
|
||||
id?: BaseRegistryItemId; // uniq id, generated automatically when not provided
|
||||
}
|
||||
|
||||
export interface BaseRegistryAddMeta {
|
||||
key?: BaseRegistryKey;
|
||||
merge?: boolean
|
||||
}
|
||||
|
||||
export class BaseRegistry<T extends BaseRegistryItem = any> {
|
||||
private items = observable.map<BaseRegistryKey, T[]>([], { 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};
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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<any>;
|
||||
}
|
||||
|
||||
export interface ClusterFeatureRegistration extends BaseRegistryItem {
|
||||
export interface ClusterFeatureRegistration {
|
||||
title: string;
|
||||
components: ClusterFeatureComponents
|
||||
feature: ClusterFeature
|
||||
|
||||
@@ -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<any>;
|
||||
}
|
||||
|
||||
export interface KubeObjectDetailRegistration extends BaseRegistryItem {
|
||||
export interface KubeObjectDetailRegistration {
|
||||
kind: string;
|
||||
apiVersions: string[];
|
||||
components: KubeObjectDetailComponents;
|
||||
|
||||
@@ -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<any>;
|
||||
}
|
||||
|
||||
export interface KubeObjectMenuRegistration extends BaseRegistryItem {
|
||||
export interface KubeObjectMenuRegistration {
|
||||
kind: string;
|
||||
apiVersions: string[];
|
||||
components: KubeObjectMenuComponents;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
// 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";
|
||||
|
||||
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"
|
||||
export interface PageMenuTarget {
|
||||
pageId: string;
|
||||
extensionId?: string;
|
||||
params?: object;
|
||||
}
|
||||
|
||||
export interface PageMenuRegistration {
|
||||
target?: PageMenuTarget;
|
||||
title: React.ReactNode;
|
||||
components: PageMenuComponents;
|
||||
subMenus?: PageSubMenuRegistration[];
|
||||
}
|
||||
|
||||
export interface PageSubMenuRegistration {
|
||||
@@ -22,13 +28,18 @@ export interface PageMenuComponents {
|
||||
}
|
||||
|
||||
export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> {
|
||||
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((menu) => {
|
||||
if (menu.target && !menu.target.extensionId) {
|
||||
menu.target.extensionId = ext.name
|
||||
}
|
||||
return menu
|
||||
})
|
||||
return super.add(normalizedItems);
|
||||
}
|
||||
}
|
||||
|
||||
export const globalPageMenuRegistry = new PageMenuRegistry<Omit<PageMenuRegistration, "subMenus">>();
|
||||
export const clusterPageMenuRegistry = new PageMenuRegistry();
|
||||
export const globalPageMenuRegistry = new PageMenuRegistry<PageMenuRegistration>();
|
||||
export const clusterPageMenuRegistry = new PageMenuRegistry<PageMenuRegistration>();
|
||||
@@ -1,13 +1,17 @@
|
||||
// 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"
|
||||
import type { PageMenuTarget } from "./page-menu-registry";
|
||||
|
||||
export interface PageRegistration extends BaseRegistryItem {
|
||||
export interface PageRegistration {
|
||||
id: string; // will be automatically prefixed with extension name
|
||||
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?: SubPageRegistration[];
|
||||
}
|
||||
|
||||
export interface SubPageRegistration {
|
||||
@@ -20,14 +24,31 @@ export interface PageComponents {
|
||||
Page: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
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<T extends PageRegistration> extends BaseRegistry<T> {
|
||||
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);
|
||||
}
|
||||
|
||||
getByPageMenuTarget(target: PageMenuTarget) {
|
||||
if (!target) {
|
||||
return null
|
||||
}
|
||||
return this.getItems().find((page) => page.routePath.startsWith(`/extension/${target.extensionId}/`) && page.id === target.pageId)
|
||||
}
|
||||
}
|
||||
|
||||
export const globalPageRegistry = new PageRegistry<Omit<PageRegistration, "subPages">>();
|
||||
export const clusterPageRegistry = new PageRegistry();
|
||||
export const globalPageRegistry = new PageRegistry<PageRegistration>();
|
||||
export const clusterPageRegistry = new PageRegistry<PageRegistration>();
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user