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/
|
||||
src/extensions/*/*.js
|
||||
src/extensions/*/*.d.ts
|
||||
src/extensions/example-extension/src/**
|
||||
locales/**/**.js
|
||||
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",
|
||||
"version": "1.0.0",
|
||||
"description": "Example extension",
|
||||
"main": "example-extension.ts",
|
||||
"main": "example-extension.js",
|
||||
"lens": {
|
||||
"metadata": {}
|
||||
"metadata": {},
|
||||
"styles": []
|
||||
},
|
||||
"dependencies": {
|
||||
}
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
},
|
||||
"include": [
|
||||
"../../../types",
|
||||
"./example-extension.ts"
|
||||
"./example-extension.tsx"
|
||||
]
|
||||
}
|
||||
|
||||
@ -80,7 +80,6 @@ export class ExtensionStore extends BaseStore<ExtensionStoreModel> {
|
||||
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,
|
||||
|
||||
@ -19,7 +19,7 @@ export class LensExtension implements ExtensionModel {
|
||||
@observable manifest: ExtensionManifest;
|
||||
@observable manifestPath: string;
|
||||
@observable isEnabled = false;
|
||||
@observable.ref runtime: Partial<LensRuntimeRendererEnv> = {};
|
||||
@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;
|
||||
|
||||
@ -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 {
|
||||
<Route component={ClusterView} {...clusterViewRoute}/>
|
||||
<Route component={ClusterSettings} {...clusterSettingsRoute}/>
|
||||
<Route component={Extensions} {...extensionsRoute}/>
|
||||
{Array.from(dynamicPages.routes).map(([path, { Main }]) => {
|
||||
return <Route key={path} path={path} component={Main}/>
|
||||
})}
|
||||
<Redirect exact to={this.startUrl}/>
|
||||
</Switch>
|
||||
</main>
|
||||
|
||||
@ -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<Props> {
|
||||
<Tooltip targetId="add-cluster-icon">
|
||||
<Trans>Add Cluster</Trans>
|
||||
</Tooltip>
|
||||
<Icon big material="add" id="add-cluster-icon" />
|
||||
<Icon big material="add" id="add-cluster-icon"/>
|
||||
{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 className="dynamic-pages">
|
||||
{Array.from(dynamicPages.all).map(([path, { MenuIcon }]) => {
|
||||
if (!MenuIcon) return;
|
||||
return <MenuIcon onClick={() => navigate(path)}/>
|
||||
{Array.from(dynamicPages.routes).map(([path, { MenuIcon }]) => {
|
||||
if (MenuIcon) {
|
||||
return <MenuIcon key={path} onClick={() => navigate(path)}/>
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -10,18 +10,18 @@ export interface PageComponents {
|
||||
}
|
||||
|
||||
export class PagesStore {
|
||||
all = observable.map<string, PageComponents>();
|
||||
routes = observable.map<string, PageComponents>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user