mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Extensions-api: initial hello-world example (#817)
Signed-off-by: Roman <ixrock@gmail.com> Co-authored-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
1 parent
f1b03990ea
commit
5daf53e6cb
19 files changed
+184
-85
No files matched your search
@@ -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")}/>
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Button, DynamicPageType, Icon, LensExtension } from "@lens/extensions"; // fixme: map to generated types from "extension-api.ts"
|
||||
import React from "react";
|
||||
import path from "path";
|
||||
|
||||
export default class ExampleExtension extends LensExtension {
|
||||
onActivate() {
|
||||
console.log('EXAMPLE EXTENSION: ACTIVATED', this.getMeta());
|
||||
this.registerPage({
|
||||
type: DynamicPageType.CLUSTER,
|
||||
path: "/extension-example",
|
||||
menuTitle: "Example Extension",
|
||||
components: {
|
||||
Page: () => <ExtensionPage extension={this}/>,
|
||||
MenuIcon: ExtensionIcon,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onDeactivate() {
|
||||
console.log('EXAMPLE EXTENSION: DEACTIVATED', this.getMeta());
|
||||
}
|
||||
}
|
||||
|
||||
export function ExtensionIcon(props: {} /*IconProps |*/) {
|
||||
return <Icon {...props} material="camera" tooltip={path.basename(__filename)}/>
|
||||
}
|
||||
|
||||
export class ExtensionPage extends React.Component<{ extension: ExampleExtension }> {
|
||||
deactivate = () => {
|
||||
const { extension } = this.props;
|
||||
extension.runtime.navigate("/")
|
||||
extension.disable();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { MainLayout } = this.props.extension.runtime.components;
|
||||
return (
|
||||
<MainLayout className="ExampleExtension">
|
||||
<div className="flex column gaps align-flex-start">
|
||||
<p>Hello from extensions-api!</p>
|
||||
<p>File: <i>{__filename}</i></p>
|
||||
<Button accent label="Deactivate" onClick={this.deactivate}/>
|
||||
</div>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
export type { LensRuntimeRendererEnv } from "./lens-runtime";
|
||||
|
||||
// APIs
|
||||
export * from "./extension"
|
||||
export * from "./lens-extension"
|
||||
export { DynamicPageType } from "./register-page";
|
||||
|
||||
// Common UI components
|
||||
export * from "../renderer/components/icon"
|
||||
|
||||
@@ -3,17 +3,17 @@ import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import { action, observable, reaction, toJS, } from "mobx";
|
||||
import { BaseStore } from "../common/base-store";
|
||||
import { ExtensionId, ExtensionManifest, ExtensionVersion, LensExtension } from "./extension";
|
||||
import { isDevelopment, isProduction, isTestEnv } from "../common/vars";
|
||||
import { ExtensionId, ExtensionManifest, ExtensionVersion, LensExtension } from "./lens-extension";
|
||||
import { isDevelopment } from "../common/vars";
|
||||
import logger from "../main/logger";
|
||||
|
||||
export interface ExtensionStoreModel {
|
||||
version: ExtensionVersion;
|
||||
extensions: Record<ExtensionId, ExtensionModel>
|
||||
extensions: [ExtensionId, ExtensionModel][]
|
||||
}
|
||||
|
||||
export interface ExtensionModel {
|
||||
id?: ExtensionId; // available in lens-extension instance
|
||||
id: ExtensionId;
|
||||
version: ExtensionVersion;
|
||||
name: string;
|
||||
manifestPath: string;
|
||||
@@ -35,7 +35,6 @@ export class ExtensionStore extends BaseStore<ExtensionStoreModel> {
|
||||
private constructor() {
|
||||
super({
|
||||
configName: "lens-extension-store",
|
||||
syncEnabled: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,7 +47,7 @@ export class ExtensionStore extends BaseStore<ExtensionStoreModel> {
|
||||
if (isDevelopment) {
|
||||
return path.resolve(__static, "../src/extensions");
|
||||
}
|
||||
return path.resolve(__static, "../extensions"); //todo figure out prod
|
||||
return path.resolve(__static, "../extensions");
|
||||
}
|
||||
|
||||
async load() {
|
||||
@@ -80,7 +79,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,
|
||||
@@ -132,7 +130,7 @@ export class ExtensionStore extends BaseStore<ExtensionStoreModel> {
|
||||
this.version = version;
|
||||
}
|
||||
if (extensions) {
|
||||
const currentExtensions = new Map(Object.entries(extensions));
|
||||
const currentExtensions = new Map(extensions);
|
||||
this.extensions.forEach(extension => {
|
||||
if (!currentExtensions.has(extension.id)) {
|
||||
this.removed.set(extension.id, extension);
|
||||
@@ -164,7 +162,7 @@ export class ExtensionStore extends BaseStore<ExtensionStoreModel> {
|
||||
toJSON(): ExtensionStoreModel {
|
||||
return toJS({
|
||||
version: this.version,
|
||||
extensions: this.extensions.toJSON(),
|
||||
extensions: Array.from(this.extensions).map(([id, instance]) => [id, instance.toJSON()]),
|
||||
}, {
|
||||
recurseEverything: true,
|
||||
})
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import type { ExtensionModel } from "./extension-store";
|
||||
import type { LensRuntimeRendererEnv } from "./lens-runtime";
|
||||
import type { PageRegistration } from "./register-page";
|
||||
import { readJsonSync } from "fs-extra";
|
||||
import { action, observable, toJS } from "mobx";
|
||||
import extensionManifest from "./example-extension/package.json"
|
||||
import logger from "../main/logger";
|
||||
|
||||
export type ExtensionId = string; // instance-id or abs path to "%lens-extension/manifest.json"
|
||||
export type ExtensionId = string | ExtensionPackageJsonPath;
|
||||
export type ExtensionPackageJsonPath = string;
|
||||
export type ExtensionVersion = string | number;
|
||||
export type ExtensionManifest = typeof extensionManifest & ExtensionModel;
|
||||
|
||||
export class LensExtension implements ExtensionModel {
|
||||
public id: ExtensionId;
|
||||
public updateUrl: string;
|
||||
protected disposers: Function[] = [];
|
||||
|
||||
@observable name = "";
|
||||
@observable description = "";
|
||||
@@ -19,7 +22,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 +44,27 @@ 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;
|
||||
this.disposers.forEach(cleanUp => cleanUp());
|
||||
this.disposers.length = 0;
|
||||
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;
|
||||
@@ -76,7 +92,7 @@ export class LensExtension implements ExtensionModel {
|
||||
}
|
||||
|
||||
toJSON(): ExtensionModel {
|
||||
return {
|
||||
return toJS({
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
version: this.version,
|
||||
@@ -84,6 +100,16 @@ export class LensExtension implements ExtensionModel {
|
||||
manifestPath: this.manifestPath,
|
||||
enabled: this.isEnabled,
|
||||
updateUrl: this.updateUrl,
|
||||
}, {
|
||||
recurseEverything: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Runtime helpers
|
||||
protected registerPage(params: PageRegistration, autoDisable = true) {
|
||||
const dispose = this.runtime.dynamicPages.register(params);
|
||||
if (autoDisable) {
|
||||
this.disposers.push(dispose);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,26 @@
|
||||
// Lens runtime for injecting to extension on activation
|
||||
import { apiManager } from "../renderer/api/api-manager";
|
||||
// Lens renderer runtime params available to extensions after activation
|
||||
|
||||
import logger from "../main/logger";
|
||||
import { dynamicPages } from "../renderer/components/cluster-manager/register-page";
|
||||
import { dynamicPages } from "./register-page";
|
||||
import { MainLayout } from "../renderer/components/layout/main-layout";
|
||||
import { navigate } from "../renderer/navigation";
|
||||
|
||||
export interface LensRuntimeRendererEnv {
|
||||
apiManager: typeof apiManager;
|
||||
navigate: typeof navigate;
|
||||
logger: typeof logger;
|
||||
dynamicPages: typeof dynamicPages
|
||||
components: {
|
||||
MainLayout: typeof MainLayout
|
||||
}
|
||||
}
|
||||
|
||||
// todo: expose more public runtime apis: stores, managers, etc.
|
||||
export function getLensRuntime(): LensRuntimeRendererEnv {
|
||||
return {
|
||||
apiManager,
|
||||
logger,
|
||||
navigate,
|
||||
dynamicPages,
|
||||
components: {
|
||||
MainLayout // fixme: refactor, import as pure component from "@lens/extensions"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Extensions-api -> Dynamic pages
|
||||
|
||||
import { computed, observable } from "mobx";
|
||||
import React from "react";
|
||||
import type { IconProps } from "../renderer/components/icon";
|
||||
|
||||
export enum DynamicPageType {
|
||||
GLOBAL = "lens-scope",
|
||||
CLUSTER = "cluster-view-scope",
|
||||
}
|
||||
|
||||
export interface PageRegistration {
|
||||
path: string; // route-path
|
||||
menuTitle: string;
|
||||
type: DynamicPageType;
|
||||
components: PageComponents;
|
||||
}
|
||||
|
||||
export interface PageComponents {
|
||||
Page: React.ComponentType<any>;
|
||||
MenuIcon: React.ComponentType<IconProps>;
|
||||
}
|
||||
|
||||
export class PagesStore {
|
||||
protected pages = observable.array<PageRegistration>([], { deep: false });
|
||||
|
||||
@computed get globalPages() {
|
||||
return this.pages.filter(page => page.type === DynamicPageType.GLOBAL);
|
||||
}
|
||||
|
||||
@computed get clusterPages() {
|
||||
return this.pages.filter(page => page.type === DynamicPageType.CLUSTER);
|
||||
}
|
||||
|
||||
// todo: verify paths to avoid collision with existing pages
|
||||
register(params: PageRegistration) {
|
||||
this.pages.push(params);
|
||||
return () => {
|
||||
this.pages.replace(
|
||||
this.pages.filter(page => page.components !== params.components)
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const dynamicPages = new PagesStore();
|
||||
Reference in new issue
Block a user