mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
extensions-api: initial hello-world example which adds new app section: menu icon + page
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
f1b03990ea
commit
f1f9a364e2
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,5 +10,6 @@ binaries/client/
|
|||||||
binaries/server/
|
binaries/server/
|
||||||
src/extensions/*/*.js
|
src/extensions/*/*.js
|
||||||
src/extensions/*/*.d.ts
|
src/extensions/*/*.d.ts
|
||||||
|
src/extensions/example-extension/src/**
|
||||||
locales/**/**.js
|
locales/**/**.js
|
||||||
lens.log
|
lens.log
|
||||||
|
|||||||
@ -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<any> {
|
|
||||||
try {
|
|
||||||
super.enable(runtime);
|
|
||||||
runtime.logger.info('EXAMPLE EXTENSION: ENABLE() override');
|
|
||||||
} catch (err){
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// <Icon material="camera" onClick={() => console.log("done")}/>
|
|
||||||
40
src/extensions/example-extension/example-extension.tsx
Normal file
40
src/extensions/example-extension/example-extension.tsx
Normal file
@ -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 <Icon {...props} material="camera" tooltip={path.basename(__filename)}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: provide extension instance and runtime params (via context or props)
|
||||||
|
export class ExtensionPage extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="ExampleExtension" style={{ padding: "20px" }}>
|
||||||
|
<div className="content flex column gaps align-flex-start">
|
||||||
|
<p>Hello from extensions-api!</p>
|
||||||
|
<p>File: <i>{__filename}</i></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,9 +2,10 @@
|
|||||||
"name": "extension-example",
|
"name": "extension-example",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Example extension",
|
"description": "Example extension",
|
||||||
"main": "example-extension.ts",
|
"main": "example-extension.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
"metadata": {}
|
"metadata": {},
|
||||||
|
"styles": []
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,6 @@
|
|||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"../../../types",
|
"../../../types",
|
||||||
"./example-extension.ts"
|
"./example-extension.tsx"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,7 +80,6 @@ export class ExtensionStore extends BaseStore<ExtensionStoreModel> {
|
|||||||
try {
|
try {
|
||||||
manifestJson = __non_webpack_require__(manifestPath); // "__non_webpack_require__" converts to native node's require()-call
|
manifestJson = __non_webpack_require__(manifestPath); // "__non_webpack_require__" converts to native node's require()-call
|
||||||
mainJs = path.resolve(path.dirname(manifestPath), manifestJson.main);
|
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);
|
const extensionModule = __non_webpack_require__(mainJs);
|
||||||
return {
|
return {
|
||||||
manifestPath: manifestPath,
|
manifestPath: manifestPath,
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export class LensExtension implements ExtensionModel {
|
|||||||
@observable manifest: ExtensionManifest;
|
@observable manifest: ExtensionManifest;
|
||||||
@observable manifestPath: string;
|
@observable manifestPath: string;
|
||||||
@observable isEnabled = false;
|
@observable isEnabled = false;
|
||||||
@observable.ref runtime: Partial<LensRuntimeRendererEnv> = {};
|
@observable.ref runtime: LensRuntimeRendererEnv;
|
||||||
|
|
||||||
constructor(model: ExtensionModel, manifest: ExtensionManifest) {
|
constructor(model: ExtensionModel, manifest: ExtensionManifest) {
|
||||||
this.importModel(model, manifest);
|
this.importModel(model, manifest);
|
||||||
@ -41,14 +41,25 @@ export class LensExtension implements ExtensionModel {
|
|||||||
this.isEnabled = true;
|
this.isEnabled = true;
|
||||||
this.runtime = runtime;
|
this.runtime = runtime;
|
||||||
console.log(`[EXTENSION]: enabled ${this.name}@${this.version}`, this.getMeta());
|
console.log(`[EXTENSION]: enabled ${this.name}@${this.version}`, this.getMeta());
|
||||||
|
this.onActivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
async disable() {
|
async disable() {
|
||||||
|
this.onDeactivate();
|
||||||
this.isEnabled = false;
|
this.isEnabled = false;
|
||||||
this.runtime = {};
|
this.runtime = null;
|
||||||
console.log(`[EXTENSION]: disabled ${this.name}@${this.version}`, this.getMeta());
|
console.log(`[EXTENSION]: disabled ${this.name}@${this.version}`, this.getMeta());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: add more hooks
|
||||||
|
protected onActivate() {
|
||||||
|
// mock
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDeactivate() {
|
||||||
|
// mock
|
||||||
|
}
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
async install(downloadUrl?: string) {
|
async install(downloadUrl?: string) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { Extensions, extensionsRoute } from "../+extensions";
|
|||||||
import { clusterViewRoute, clusterViewURL, getMatchedCluster, getMatchedClusterId } from "./cluster-view.route";
|
import { clusterViewRoute, clusterViewURL, getMatchedCluster, getMatchedClusterId } from "./cluster-view.route";
|
||||||
import { clusterStore } from "../../../common/cluster-store";
|
import { clusterStore } from "../../../common/cluster-store";
|
||||||
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
|
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
|
||||||
|
import { dynamicPages } from "./register-page";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ClusterManager extends React.Component {
|
export class ClusterManager extends React.Component {
|
||||||
@ -62,6 +63,9 @@ export class ClusterManager extends React.Component {
|
|||||||
<Route component={ClusterView} {...clusterViewRoute}/>
|
<Route component={ClusterView} {...clusterViewRoute}/>
|
||||||
<Route component={ClusterSettings} {...clusterSettingsRoute}/>
|
<Route component={ClusterSettings} {...clusterSettingsRoute}/>
|
||||||
<Route component={Extensions} {...extensionsRoute}/>
|
<Route component={Extensions} {...extensionsRoute}/>
|
||||||
|
{Array.from(dynamicPages.routes).map(([path, { Main }]) => {
|
||||||
|
return <Route key={path} path={path} component={Main}/>
|
||||||
|
})}
|
||||||
<Redirect exact to={this.startUrl}/>
|
<Redirect exact to={this.startUrl}/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { ClusterId, clusterStore } from "../../../common/cluster-store";
|
|||||||
import { workspaceStore } from "../../../common/workspace-store";
|
import { workspaceStore } from "../../../common/workspace-store";
|
||||||
import { ClusterIcon } from "../cluster-icon";
|
import { ClusterIcon } from "../cluster-icon";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { cssNames, IClassName, autobind } from "../../utils";
|
import { autobind, cssNames, IClassName } from "../../utils";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
import { addClusterURL } from "../+add-cluster";
|
import { addClusterURL } from "../+add-cluster";
|
||||||
@ -20,7 +20,7 @@ import { Tooltip } from "../tooltip";
|
|||||||
import { ConfirmDialog } from "../confirm-dialog";
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
import { clusterIpc } from "../../../common/cluster-ipc";
|
||||||
import { clusterViewURL, getMatchedClusterId } from "./cluster-view.route";
|
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";
|
import { dynamicPages } from "./register-page";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -143,15 +143,16 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
<Tooltip targetId="add-cluster-icon">
|
<Tooltip targetId="add-cluster-icon">
|
||||||
<Trans>Add Cluster</Trans>
|
<Trans>Add Cluster</Trans>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Icon big material="add" id="add-cluster-icon" />
|
<Icon big material="add" id="add-cluster-icon"/>
|
||||||
{newContexts.size > 0 && (
|
{newContexts.size > 0 && (
|
||||||
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>} />
|
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>}/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="dynamic-pages">
|
<div className="dynamic-pages">
|
||||||
{Array.from(dynamicPages.all).map(([path, { MenuIcon }]) => {
|
{Array.from(dynamicPages.routes).map(([path, { MenuIcon }]) => {
|
||||||
if (!MenuIcon) return;
|
if (MenuIcon) {
|
||||||
return <MenuIcon onClick={() => navigate(path)}/>
|
return <MenuIcon key={path} onClick={() => navigate(path)}/>
|
||||||
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,18 +10,18 @@ export interface PageComponents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PagesStore {
|
export class PagesStore {
|
||||||
all = observable.map<string, PageComponents>();
|
routes = observable.map<string, PageComponents>();
|
||||||
|
|
||||||
getComponents(path: string): PageComponents | null {
|
getComponents(path: string): PageComponents | null {
|
||||||
return this.all.get(path);
|
return this.routes.get(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
register(path: string, components: PageComponents) {
|
register(path: string, components: PageComponents) {
|
||||||
this.all.set(path, components);
|
this.routes.set(path, components);
|
||||||
}
|
}
|
||||||
|
|
||||||
unregister(path: string) {
|
unregister(path: string) {
|
||||||
this.all.delete(path);
|
this.routes.delete(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user