1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/extensions/registries/page-registry.ts
Sebastian Malton c93ee4ea6d
[BREAKING]: remove deprecated routePath before GA (#1505)
Signed-off-by: Sebastian Malton <sebastian@malton.name>
2020-11-25 12:03:57 +02:00

90 lines
3.6 KiB
TypeScript

// Extensions-api -> Custom page registration
import type { PageMenuTarget } from "./page-menu-registry";
import type React from "react";
import path from "path";
import { action } from "mobx";
import { compile } from "path-to-regexp";
import { BaseRegistry } from "./base-registry";
import { LensExtension, sanitizeExtensionName } from "../lens-extension";
import logger from "../../main/logger";
import { recitfy } from "../../common/utils";
export interface PageRegistration {
/**
* Page ID or additional route path to indicate uniqueness within current extension registered pages
* Might contain special url placeholders, e.g. "/users/:userId?" (? - marks as optional param)
* When not provided, first registered page without "id" would be used for page-menus without target.pageId for same extension
*/
id?: string;
/**
* Strict route matching to provided page-id, read also: https://reactrouter.com/web/api/NavLink/exact-bool
* In case when more than one page registered at same extension "pageId" is required to identify different pages,
* It might be useful to provide `exact: true` in some cases to avoid overlapping routes.
* Without {exact:true} second page never matches since first page-id/route already includes partial route.
* @example const pages = [
* {id: "/users", exact: true},
* {id: "/users/:userId?"}
* ]
* Pro-tip: registering pages in opposite order will make same effect without "exact".
*/
exact?: boolean;
components: PageComponents;
}
export interface RegisteredPage extends PageRegistration {
extensionId: string; // required for compiling registered page to url with page-menu-target to compare
routePath: string; // full route-path to registered extension page
}
export interface PageComponents {
Page: React.ComponentType<any>;
}
export function getExtensionPageUrl<P extends object>({ extensionId, pageId = "", params }: PageMenuTarget<P>): string {
const extensionBaseUrl = compile(`/extension/:name`)({
name: sanitizeExtensionName(extensionId), // compile only with extension-id first and define base path
});
const extPageRoutePath = path.join(extensionBaseUrl, pageId); // page-id might contain route :param-s, so don't compile yet
if (params) {
return compile(extPageRoutePath)(params); // might throw error when required params not passed
}
return extPageRoutePath;
}
export class PageRegistry extends BaseRegistry<RegisteredPage> {
@action
add(items: PageRegistration | PageRegistration[], ext: LensExtension) {
const itemArray = recitfy(items);
let registeredPages: RegisteredPage[] = [];
try {
registeredPages = itemArray.map(page => ({
...page,
extensionId: ext.name,
routePath: getExtensionPageUrl({ extensionId: ext.name, pageId: page.id }),
}));
} catch (err) {
logger.error(`[EXTENSION]: page-registration failed`, {
items,
extension: ext,
error: String(err),
});
}
return super.add(registeredPages);
}
getUrl<P extends object>({ extensionId, id: pageId }: RegisteredPage, params?: P) {
return getExtensionPageUrl({ extensionId, pageId, params });
}
getByPageMenuTarget(target: PageMenuTarget = {}): RegisteredPage | null {
const targetUrl = getExtensionPageUrl(target);
return this.getItems().find(({ id: pageId, extensionId }) => {
const pageUrl = getExtensionPageUrl({ extensionId, pageId, params: target.params }); // compiled with provided params
return targetUrl === pageUrl;
}) || null;
}
}
export const globalPageRegistry = new PageRegistry();
export const clusterPageRegistry = new PageRegistry();