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

fixes, tweak example-extension for demo

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2020-12-03 19:41:53 +02:00
parent b7be386e6b
commit cd7f906afc
9 changed files with 70 additions and 47 deletions

View File

@ -1,12 +1,19 @@
import { LensRendererExtension, Component } from "@k8slens/extensions"; import { Component, LensRendererExtension, Navigation } from "@k8slens/extensions";
import { CoffeeDoodle } from "react-open-doodles"; import { CoffeeDoodle } from "react-open-doodles";
import path from "path";
import React from "react"; import React from "react";
import path from "path";
import { observer } from "mobx-react";
export const exampleNameUrlParam = Navigation.createUrlParam<string>({
name: "name",
defaultValue: "demo",
});
export function ExampleIcon(props: Component.IconProps) { export function ExampleIcon(props: Component.IconProps) {
return <Component.Icon {...props} material="pages" tooltip={path.basename(__filename)}/>; return <Component.Icon {...props} material="pages" tooltip={path.basename(__filename)}/>;
} }
@observer
export class ExamplePage extends React.Component<{ extension: LensRendererExtension }> { export class ExamplePage extends React.Component<{ extension: LensRendererExtension }> {
deactivate = () => { deactivate = () => {
const { extension } = this.props; const { extension } = this.props;
@ -15,16 +22,24 @@ export class ExamplePage extends React.Component<{ extension: LensRendererExtens
}; };
render() { render() {
const exampleName = exampleNameUrlParam.get();
const doodleStyle = { const doodleStyle = {
width: "200px" width: "200px"
}; };
return ( return (
<div className="flex column gaps align-flex-start"> <div className="flex column gaps align-flex-start" style={{ padding: 24 }}>
<div style={doodleStyle}><CoffeeDoodle accent="#3d90ce" /></div> <div style={doodleStyle}><CoffeeDoodle accent="#3d90ce"/></div>
<p>Hello from Example extension!</p> <p>Hello from Example extension!</p>
<p>File: <i>{__filename}</i></p> <p>File: <i>{__filename}</i></p>
<p>Location: <i>{location.href}</i></p>
<p className="url-params-demo flex column gaps">
<a onClick={() => exampleNameUrlParam.set("secret")}>Show secret button</a>
{exampleName === "secret" && (
<Component.Button accent label="Deactivate" onClick={this.deactivate}/> <Component.Button accent label="Deactivate" onClick={this.deactivate}/>
)}
</p>
</div> </div>
); );
} }

View File

@ -1,5 +1,5 @@
import { LensRendererExtension } from "@k8slens/extensions"; import { LensRendererExtension } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page"; import { ExampleIcon, ExamplePage, exampleNameUrlParam } from "./page";
import React from "react"; import React from "react";
export default class ExampleExtension extends LensRendererExtension { export default class ExampleExtension extends LensRendererExtension {
@ -9,13 +9,16 @@ export default class ExampleExtension extends LensRendererExtension {
title: "Example Extension", title: "Example Extension",
components: { components: {
Page: () => <ExamplePage extension={this}/>, Page: () => <ExamplePage extension={this}/>,
} },
params: [
exampleNameUrlParam,
]
} }
]; ];
clusterPageMenus = [ clusterPageMenus = [
{ {
target: { pageId: "example", params: {} }, target: { pageId: "example" },
title: "Example Extension", title: "Example Extension",
components: { components: {
Icon: ExampleIcon, Icon: ExampleIcon,

View File

@ -29,7 +29,7 @@ export interface PageComponents {
export interface PageTarget<P = {}> { export interface PageTarget<P = {}> {
extensionId?: string; extensionId?: string;
pageId?: string; pageId?: string;
params?: Record<string, any | any[]> & P; params?: Record<string, any | any[]> & P; // default target page params
} }
export interface RegisteredPage extends PageRegistration { export interface RegisteredPage extends PageRegistration {
@ -38,26 +38,23 @@ export interface RegisteredPage extends PageRegistration {
} }
export function getExtensionPageUrl<P extends object>(target: PageTarget): string { export function getExtensionPageUrl<P extends object>(target: PageTarget): string {
const { extensionId, pageId = "", params } = target; const { extensionId, pageId = "", params: targetParams = {} } = target;
let stringifiedParams = ""; let stringifiedParams = "";
// stringify params to matched target page // stringify params to matched target page
if (params) {
const page = globalPageRegistry.getByPageTarget(target) || clusterPageRegistry.getByPageTarget(target); const page = globalPageRegistry.getByPageTarget(target) || clusterPageRegistry.getByPageTarget(target);
if (page?.params) { if (page?.params) {
const searchParams: string[] = []; const searchParams = page.params.map(urlParam => {
page.params.forEach(urlParam => { return urlParam.toSearchString({
const paramValue = params[urlParam.name]; value: targetParams[urlParam.name] ?? urlParam.getDefaultValue(),
if (paramValue == undefined) return; mergeGlobals: false,
searchParams.push( withPrefix: false,
urlParam.toSearchString(paramValue, { mergeGlobals: false, withPrefix: false }) // e.g. "param=value" });
);
}); });
if (searchParams.length > 0) { if (searchParams.length > 0) {
stringifiedParams = `?${searchParams.join("&")}`; stringifiedParams = `?${searchParams.join("&")}`;
} }
} }
}
return path.posix.join("/extension", sanitizeExtensionName(extensionId), pageId, stringifiedParams); return path.posix.join("/extension", sanitizeExtensionName(extensionId), pageId, stringifiedParams);
} }

View File

@ -1,3 +1,3 @@
export { navigate, UrlParamInit, isActiveRoute, createUrlParam, UrlParam } from "../../renderer/navigation"; export { createUrlParam, navigate, isActiveRoute, UrlParamInit, UrlParam } from "../../renderer/navigation";
export { hideDetails, showDetails, getDetailsUrl } from "../../renderer/components/kube-object/kube-object-details"; export { hideDetails, showDetails, getDetailsUrl } from "../../renderer/components/kube-object/kube-object-details";
export { IURLParams } from "../../common/utils/buildUrl"; export { IURLParams } from "../../common/utils/buildUrl";

View File

@ -34,7 +34,7 @@ import { Terminal } from "./dock/terminal";
import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store"; import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store";
import logger from "../../main/logger"; import logger from "../../main/logger";
import { webFrame } from "electron"; import { webFrame } from "electron";
import { clusterPageRegistry } from "../../extensions/registries/page-registry"; import { clusterPageRegistry, getExtensionPageUrl } from "../../extensions/registries/page-registry";
import { extensionLoader } from "../../extensions/extension-loader"; import { extensionLoader } from "../../extensions/extension-loader";
import { appEventBus } from "../../common/event-bus"; import { appEventBus } from "../../common/event-bus";
import { broadcastMessage, requestMain } from "../../common/ipc"; import { broadcastMessage, requestMain } from "../../common/ipc";
@ -126,14 +126,14 @@ export class App extends React.Component {
if (!menuItem.id) { if (!menuItem.id) {
return routes; return routes;
} }
clusterPageMenuRegistry.getSubItems(menuItem).forEach((item) => { clusterPageMenuRegistry.getSubItems(menuItem).forEach((subMenu) => {
const page = clusterPageRegistry.getByPageTarget(item.target); const page = clusterPageRegistry.getByPageTarget(subMenu.target);
if (page) { if (page) {
routes.push({ routes.push({
routePath: page.url, routePath: page.url,
url: page.url, url: getExtensionPageUrl(subMenu.target),
title: item.title, title: subMenu.title,
component: page.components.Page, component: page.components.Page,
}); });
} }

View File

@ -22,7 +22,7 @@ import { landingURL } from "../+landing-page";
import { Tooltip } from "../tooltip"; import { Tooltip } from "../tooltip";
import { ConfirmDialog } from "../confirm-dialog"; import { ConfirmDialog } from "../confirm-dialog";
import { clusterViewURL } from "./cluster-view.route"; import { clusterViewURL } from "./cluster-view.route";
import { globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries"; import { getExtensionPageUrl, globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries";
import { clusterDisconnectHandler } from "../../../common/cluster-ipc"; import { clusterDisconnectHandler } from "../../../common/cluster-ipc";
interface Props { interface Props {
@ -159,13 +159,16 @@ export class ClustersMenu extends React.Component<Props> {
<div className="extensions"> <div className="extensions">
{globalPageMenuRegistry.getItems().map(({ title, target, components: { Icon } }) => { {globalPageMenuRegistry.getItems().map(({ title, target, components: { Icon } }) => {
const registeredPage = globalPageRegistry.getByPageTarget(target); const registeredPage = globalPageRegistry.getByPageTarget(target);
if (!registeredPage) return; if (!registeredPage){
const { url: pageUrl } = registeredPage; return;
}
const pageUrl = getExtensionPageUrl(target);
const isActive = isActiveRoute(registeredPage.url);
return ( return (
<Icon <Icon
key={pageUrl} key={pageUrl}
tooltip={title} tooltip={title}
active={isActiveRoute(pageUrl)} active={isActive}
onClick={() => navigate(pageUrl)} onClick={() => navigate(pageUrl)}
/> />
); );

View File

@ -37,7 +37,7 @@ export function hideDetails() {
} }
export function getDetailsUrl(details: string, resetSelected = false) { export function getDetailsUrl(details: string, resetSelected = false) {
const detailsUrl = kubeDetailsUrlParam.toSearchString(details); const detailsUrl = kubeDetailsUrlParam.toSearchString({ value: details });
if (resetSelected) { if (resetSelected) {
const params = new URLSearchParams(detailsUrl); const params = new URLSearchParams(detailsUrl);
params.delete(kubeSelectedUrlParam.name); params.delete(kubeSelectedUrlParam.name);

View File

@ -29,7 +29,7 @@ import { CustomResources } from "../+custom-resources/custom-resources";
import { isActiveRoute } from "../../navigation"; import { isActiveRoute } from "../../navigation";
import { isAllowedResource } from "../../../common/rbac"; import { isAllowedResource } from "../../../common/rbac";
import { Spinner } from "../spinner"; import { Spinner } from "../spinner";
import { ClusterPageMenuRegistration, clusterPageMenuRegistry, clusterPageRegistry } from "../../../extensions/registries"; import { ClusterPageMenuRegistration, clusterPageMenuRegistry, clusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries";
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false }); const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
@ -84,13 +84,14 @@ export class Sidebar extends React.Component<Props> {
return routes; return routes;
} }
clusterPageMenuRegistry.getSubItems(menu).forEach((subItem) => { clusterPageMenuRegistry.getSubItems(menu).forEach((subMenu) => {
const subPage = clusterPageRegistry.getByPageTarget(subItem.target); const subPage = clusterPageRegistry.getByPageTarget(subMenu.target);
if (subPage) { if (subPage) {
const { extensionId, id: pageId } = subPage;
routes.push({ routes.push({
routePath: subPage.url, routePath: subPage.url,
url: subPage.url, url: getExtensionPageUrl({ extensionId, pageId, params: subMenu.target.params }),
title: subItem.title, title: subMenu.title,
component: subPage.components.Page, component: subPage.components.Page,
}); });
} }
@ -106,12 +107,12 @@ export class Sidebar extends React.Component<Props> {
return; return;
} }
let pageUrl = getExtensionPageUrl(menuItem.target);
let isActive = isActiveRoute(registeredPage.url);
const tabRoutes = this.getTabLayoutRoutes(menuItem); const tabRoutes = this.getTabLayoutRoutes(menuItem);
let pageUrl = registeredPage.url;
let isActive = isActiveRoute(pageUrl);
if (tabRoutes.length > 0) { if (tabRoutes.length > 0) {
pageUrl = (tabRoutes.find(tab => tab.default) || tabRoutes[0]).url; pageUrl = tabRoutes[0].url;
isActive = isActiveRoute(tabRoutes.map((tab) => tab.routePath)); isActive = isActiveRoute(tabRoutes.map((tab) => tab.routePath));
} }

View File

@ -54,7 +54,7 @@ export class UrlParam<V = any | any[]> {
get(): V { get(): V {
const { history, urlName } = this; const { history, urlName } = this;
const { multiValueSep, multiValues, defaultValue, skipEmpty } = this.init; const { multiValueSep, defaultValue, skipEmpty } = this.init;
const value = this.parse(history.searchParams.getAsArray(urlName, multiValueSep)); const value = this.parse(history.searchParams.getAsArray(urlName, multiValueSep));
if (skipEmpty && this.isEmpty(value)) { if (skipEmpty && this.isEmpty(value)) {
@ -64,19 +64,23 @@ export class UrlParam<V = any | any[]> {
} }
set(value: V, { mergeGlobals = true, replaceHistory = false } = {}) { set(value: V, { mergeGlobals = true, replaceHistory = false } = {}) {
const search = this.toSearchString(value, { mergeGlobals }); const search = this.toSearchString({ mergeGlobals, value });
this.history.merge({ search }, replaceHistory); this.history.merge({ search }, replaceHistory);
} }
getDefaultValue(){
return this.init.defaultValue;
}
isDefault() { isDefault() {
return this.get() === this.init.defaultValue; return this.get() === this.getDefaultValue();
} }
clear() { clear() {
this.history.searchParams.delete(this.urlName); this.history.searchParams.delete(this.urlName);
} }
toSearchString(value = this.get(), { withPrefix = true, mergeGlobals = true } = {}): string { toSearchString({ withPrefix = true, mergeGlobals = true, value = this.get() } = {}): string {
const { history, urlName, init: { skipEmpty } } = this; const { history, urlName, init: { skipEmpty } } = this;
const searchParams = new URLSearchParams(mergeGlobals ? history.location.search : ""); const searchParams = new URLSearchParams(mergeGlobals ? history.location.search : "");