From f1f9a364e265f8dda4333cc007bd94d89f2a4719 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 7 Sep 2020 16:48:10 +0300 Subject: [PATCH] extensions-api: initial hello-world example which adds new app section: menu icon + page Signed-off-by: Roman --- .gitignore | 1 + .../example-extension/example-extension.ts | 17 -------- .../example-extension/example-extension.tsx | 40 +++++++++++++++++++ src/extensions/example-extension/package.json | 5 ++- .../example-extension/tsconfig.json | 2 +- src/extensions/extension-store.ts | 1 - src/extensions/extension.ts | 15 ++++++- .../cluster-manager/cluster-manager.tsx | 4 ++ .../cluster-manager/clusters-menu.tsx | 15 +++---- .../cluster-manager/register-page.ts | 8 ++-- 10 files changed, 74 insertions(+), 34 deletions(-) delete mode 100644 src/extensions/example-extension/example-extension.ts create mode 100644 src/extensions/example-extension/example-extension.tsx diff --git a/.gitignore b/.gitignore index 6ddf3f489c..11cc6298ab 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,6 @@ binaries/client/ binaries/server/ src/extensions/*/*.js src/extensions/*/*.d.ts +src/extensions/example-extension/src/** locales/**/**.js lens.log diff --git a/src/extensions/example-extension/example-extension.ts b/src/extensions/example-extension/example-extension.ts deleted file mode 100644 index 28b4da5825..0000000000 --- a/src/extensions/example-extension/example-extension.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { LensExtension, Icon, LensRuntimeRendererEnv } from "@lens/extensions"; // fixme: map to generated types from "extension-api.d.ts" - -// todo: register custom icon in cluster-menu -// todo: register custom view by clicking the item - -export default class ExampleExtension extends LensExtension { - async enable(runtime: /*LensRuntimeRendererEnv*/ any): Promise { - try { - super.enable(runtime); - runtime.logger.info('EXAMPLE EXTENSION: ENABLE() override'); - } catch (err){ - console.error(err) - } - } -} - -// console.log("done")}/> \ No newline at end of file diff --git a/src/extensions/example-extension/example-extension.tsx b/src/extensions/example-extension/example-extension.tsx new file mode 100644 index 0000000000..5a4e7032b9 --- /dev/null +++ b/src/extensions/example-extension/example-extension.tsx @@ -0,0 +1,40 @@ +import { Icon, LensExtension } from "@lens/extensions"; // fixme: map to generated types from "extension-api.d.ts" +import React from "react"; +import path from "path"; + +export default class ExampleExtension extends LensExtension { + protected routePath = "/extension-example" + + onActivate() { + console.log('EXAMPLE EXTENSION: ACTIVATE', this.getMeta()) + const { dynamicPages } = this.runtime; + dynamicPages.register(this.routePath, { + Main: ExtensionPage, + MenuIcon: ExtensionIcon, + }) + } + + onDeactivate() { + console.log('EXAMPLE EXTENSION: DEACTIVATE', this.getMeta()); + const { dynamicPages } = this.runtime; + dynamicPages.unregister(this.routePath); + } +} + +export function ExtensionIcon(props: {} /*IconProps |*/) { + return +} + +// todo: provide extension instance and runtime params (via context or props) +export class ExtensionPage extends React.Component { + render() { + return ( +
+
+

Hello from extensions-api!

+

File: {__filename}

+
+
+ ) + } +} diff --git a/src/extensions/example-extension/package.json b/src/extensions/example-extension/package.json index d57a78aab5..663d5d432a 100644 --- a/src/extensions/example-extension/package.json +++ b/src/extensions/example-extension/package.json @@ -2,9 +2,10 @@ "name": "extension-example", "version": "1.0.0", "description": "Example extension", - "main": "example-extension.ts", + "main": "example-extension.js", "lens": { - "metadata": {} + "metadata": {}, + "styles": [] }, "dependencies": { } diff --git a/src/extensions/example-extension/tsconfig.json b/src/extensions/example-extension/tsconfig.json index 16081f6055..993992112a 100644 --- a/src/extensions/example-extension/tsconfig.json +++ b/src/extensions/example-extension/tsconfig.json @@ -8,6 +8,6 @@ }, "include": [ "../../../types", - "./example-extension.ts" + "./example-extension.tsx" ] } diff --git a/src/extensions/extension-store.ts b/src/extensions/extension-store.ts index 655c4d6ca7..e794626942 100644 --- a/src/extensions/extension-store.ts +++ b/src/extensions/extension-store.ts @@ -80,7 +80,6 @@ export class ExtensionStore extends BaseStore { try { manifestJson = __non_webpack_require__(manifestPath); // "__non_webpack_require__" converts to native node's require()-call mainJs = path.resolve(path.dirname(manifestPath), manifestJson.main); - mainJs = mainJs.replace(/\.ts$/i, ".js"); // todo: compile *.ts on the fly? const extensionModule = __non_webpack_require__(mainJs); return { manifestPath: manifestPath, diff --git a/src/extensions/extension.ts b/src/extensions/extension.ts index 698422fb54..ec40245a49 100644 --- a/src/extensions/extension.ts +++ b/src/extensions/extension.ts @@ -19,7 +19,7 @@ export class LensExtension implements ExtensionModel { @observable manifest: ExtensionManifest; @observable manifestPath: string; @observable isEnabled = false; - @observable.ref runtime: Partial = {}; + @observable.ref runtime: LensRuntimeRendererEnv; constructor(model: ExtensionModel, manifest: ExtensionManifest) { this.importModel(model, manifest); @@ -41,14 +41,25 @@ export class LensExtension implements ExtensionModel { this.isEnabled = true; this.runtime = runtime; console.log(`[EXTENSION]: enabled ${this.name}@${this.version}`, this.getMeta()); + this.onActivate(); } async disable() { + this.onDeactivate(); this.isEnabled = false; - this.runtime = {}; + this.runtime = null; console.log(`[EXTENSION]: disabled ${this.name}@${this.version}`, this.getMeta()); } + // todo: add more hooks + protected onActivate() { + // mock + } + + protected onDeactivate() { + // mock + } + // todo async install(downloadUrl?: string) { return; diff --git a/src/renderer/components/cluster-manager/cluster-manager.tsx b/src/renderer/components/cluster-manager/cluster-manager.tsx index 987f22658d..08cbfa9207 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.tsx +++ b/src/renderer/components/cluster-manager/cluster-manager.tsx @@ -15,6 +15,7 @@ import { Extensions, extensionsRoute } from "../+extensions"; import { clusterViewRoute, clusterViewURL, getMatchedCluster, getMatchedClusterId } from "./cluster-view.route"; import { clusterStore } from "../../../common/cluster-store"; import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views"; +import { dynamicPages } from "./register-page"; @observer export class ClusterManager extends React.Component { @@ -62,6 +63,9 @@ export class ClusterManager extends React.Component { + {Array.from(dynamicPages.routes).map(([path, { Main }]) => { + return + })} diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx index 495ce2176a..6068a5bbe1 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.tsx +++ b/src/renderer/components/cluster-manager/clusters-menu.tsx @@ -10,7 +10,7 @@ import { ClusterId, clusterStore } from "../../../common/cluster-store"; import { workspaceStore } from "../../../common/workspace-store"; import { ClusterIcon } from "../cluster-icon"; import { Icon } from "../icon"; -import { cssNames, IClassName, autobind } from "../../utils"; +import { autobind, cssNames, IClassName } from "../../utils"; import { Badge } from "../badge"; import { navigate } from "../../navigation"; import { addClusterURL } from "../+add-cluster"; @@ -20,7 +20,7 @@ import { Tooltip } from "../tooltip"; import { ConfirmDialog } from "../confirm-dialog"; import { clusterIpc } from "../../../common/cluster-ipc"; import { clusterViewURL, getMatchedClusterId } from "./cluster-view.route"; -import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd"; +import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd"; import { dynamicPages } from "./register-page"; interface Props { @@ -143,15 +143,16 @@ export class ClustersMenu extends React.Component { Add Cluster - + {newContexts.size > 0 && ( - new} /> + new}/> )}
- {Array.from(dynamicPages.all).map(([path, { MenuIcon }]) => { - if (!MenuIcon) return; - return navigate(path)}/> + {Array.from(dynamicPages.routes).map(([path, { MenuIcon }]) => { + if (MenuIcon) { + return navigate(path)}/> + } })}
diff --git a/src/renderer/components/cluster-manager/register-page.ts b/src/renderer/components/cluster-manager/register-page.ts index 71a6345cae..23db2c36dd 100644 --- a/src/renderer/components/cluster-manager/register-page.ts +++ b/src/renderer/components/cluster-manager/register-page.ts @@ -10,18 +10,18 @@ export interface PageComponents { } export class PagesStore { - all = observable.map(); + routes = observable.map(); getComponents(path: string): PageComponents | null { - return this.all.get(path); + return this.routes.get(path); } register(path: string, components: PageComponents) { - this.all.set(path, components); + this.routes.set(path, components); } unregister(path: string) { - this.all.delete(path); + this.routes.delete(path); } }