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

fixes & refactoring

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2020-11-07 12:00:07 +02:00
parent 10570423fc
commit 5643e84401
5 changed files with 80 additions and 61 deletions

View File

@ -16,46 +16,34 @@ export function extensionPackagesRoot() {
} }
export class ExtensionLoader { export class ExtensionLoader {
@observable isLoaded = false;
protected extensions = observable.map<LensExtensionId, InstalledExtension>(); protected extensions = observable.map<LensExtensionId, InstalledExtension>();
protected instances = observable.map<LensExtensionId, LensExtension>() protected instances = observable.map<LensExtensionId, LensExtension>();
@observable isLoaded = false;
whenLoaded = when(() => this.isLoaded);
constructor() { constructor() {
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.on("extensions:loaded", (event, extensions: InstalledExtension[]) => { ipcRenderer.on("extensions:loaded", (event, extensions: [LensExtensionId, InstalledExtension][]) => {
this.isLoaded = true; this.isLoaded = true;
extensions.forEach((ext) => { extensions.forEach(([extId, ext]) => {
if (!this.extensions.has(ext.manifestPath)) { if (!this.extensions.has(extId)) {
this.extensions.set(ext.manifestPath, ext) this.extensions.set(extId, ext)
} }
}) })
}); });
} }
this.manageExtensionsState(); extensionsStore.manageState(this);
} }
@computed get userExtensions(): InstalledExtension[] { @computed get userExtensions(): Map<LensExtensionId, InstalledExtension> {
return Array.from(this.toJSON().values()).filter(ext => !ext.isBundled) const extensions = this.extensions.toJS();
} extensions.forEach((ext, extId) => {
if (ext.isBundled) {
protected async manageExtensionsState() { extensions.delete(extId);
await extensionsStore.whenLoaded;
await when(() => this.isLoaded);
// apply initial state
this.extensions.forEach((ext, extId) => {
ext.enabled = ext.isBundled || extensionsStore.isEnabled(extId);
})
// handle updated state from store
reaction(() => extensionsStore.extensions.toJS(), extensionsState => {
extensionsState.forEach((state, extId) => {
const ext = this.extensions.get(extId);
if (ext && !ext.isBundled && ext.enabled !== state.enabled) {
ext.enabled = state.enabled;
} }
}) })
}); return extensions;
} }
@action @action
@ -93,14 +81,13 @@ export class ExtensionLoader {
} }
protected autoInitExtensions(register: (ext: LensExtension) => Function[]) { protected autoInitExtensions(register: (ext: LensExtension) => Function[]) {
return reaction(() => this.toJSON(), (installedExtensions) => { return reaction(() => this.toJSON(), installedExtensions => {
for (const [extId, ext] of installedExtensions) { for (const [extId, ext] of installedExtensions) {
let instance = this.instances.get(extId); let instance = this.instances.get(extId);
if (ext.enabled && !instance) { if (ext.isEnabled && !instance) {
try { try {
const extensionModule = this.requireExtension(ext) const LensExtensionClass: LensExtensionConstructor = this.requireExtension(ext)
if (!extensionModule) continue; if (!LensExtensionClass) continue;
const LensExtensionClass: LensExtensionConstructor = extensionModule.default;
instance = new LensExtensionClass(ext); instance = new LensExtensionClass(ext);
instance.whenEnabled(() => register(instance)); instance.whenEnabled(() => register(instance));
instance.enable(); instance.enable();
@ -108,7 +95,7 @@ export class ExtensionLoader {
} catch (err) { } catch (err) {
logger.error(`[EXTENSION-LOADER]: activation extension error`, { ext, err }) logger.error(`[EXTENSION-LOADER]: activation extension error`, { ext, err })
} }
} else if (!ext.enabled && instance) { } else if (!ext.isEnabled && instance) {
try { try {
instance.disable(); instance.disable();
this.instances.delete(extId); this.instances.delete(extId);
@ -131,7 +118,7 @@ export class ExtensionLoader {
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main)) extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main))
} }
if (extEntrypoint !== "") { if (extEntrypoint !== "") {
return __non_webpack_require__(extEntrypoint) return __non_webpack_require__(extEntrypoint).default;
} }
} catch (err) { } catch (err) {
console.error(`[EXTENSION-LOADER]: can't load extension main at ${extEntrypoint}: ${err}`, { extension }); console.error(`[EXTENSION-LOADER]: can't load extension main at ${extEntrypoint}: ${err}`, { extension });
@ -139,7 +126,11 @@ export class ExtensionLoader {
} }
} }
toJSON() { getExtension(extId: LensExtensionId): InstalledExtension {
return this.extensions.get(extId);
}
toJSON(): Map<LensExtensionId, InstalledExtension> {
return toJS(this.extensions, { return toJS(this.extensions, {
exportMapsAsObjects: false, exportMapsAsObjects: false,
recurseEverything: true, recurseEverything: true,
@ -153,7 +144,7 @@ export class ExtensionLoader {
frameId: frameId, frameId: frameId,
frameOnly: !!frameId, frameOnly: !!frameId,
args: [ args: [
Array.from(this.toJSON().values()), Array.from(this.toJSON()),
], ],
}) })
} }

View File

@ -10,8 +10,8 @@ import { getBundledExtensions } from "../common/utils/app-version"
export interface InstalledExtension { export interface InstalledExtension {
readonly manifest: LensExtensionManifest; readonly manifest: LensExtensionManifest;
readonly manifestPath: string; readonly manifestPath: string;
readonly isBundled?: boolean; // defined in package.json readonly isBundled?: boolean; // defined in project root's package.json
enabled?: boolean; isEnabled: boolean;
} }
type Dependencies = { type Dependencies = {
@ -90,6 +90,7 @@ export class ExtensionManager {
manifestPath: path.join(this.nodeModulesPath, manifestJson.name, "package.json"), manifestPath: path.join(this.nodeModulesPath, manifestJson.name, "package.json"),
manifest: manifestJson, manifest: manifestJson,
isBundled: isBundled, isBundled: isBundled,
isEnabled: isBundled,
} }
} catch (err) { } catch (err) {
logger.error(`[EXTENSION-MANAGER]: can't install extension at ${manifestPath}: ${err}`, { manifestJson }); logger.error(`[EXTENSION-MANAGER]: can't install extension at ${manifestPath}: ${err}`, { manifestJson });

View File

@ -1,6 +1,7 @@
import type { LensExtensionId } from "./lens-extension"; import type { LensExtensionId } from "./lens-extension";
import type { ExtensionLoader } from "./extension-loader";
import { BaseStore } from "../common/base-store" import { BaseStore } from "../common/base-store"
import { action, observable, toJS } from "mobx"; import { action, observable, reaction, toJS } from "mobx";
export interface LensExtensionsStoreModel { export interface LensExtensionsStoreModel {
extensions: Record<LensExtensionId, LensExtensionState>; extensions: Record<LensExtensionId, LensExtensionState>;
@ -13,33 +14,60 @@ export interface LensExtensionState {
export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> { export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
constructor() { constructor() {
super({ super({
configName: "lens-extensions" configName: "lens-extensions",
}); });
} }
@observable extensions = observable.map<LensExtensionId, LensExtensionState>(); protected state = observable.map<LensExtensionId, LensExtensionState>();
@action protected getState(extensionLoader: ExtensionLoader) {
setEnabled(extId: LensExtensionId, enabled: boolean) { const state: Record<LensExtensionId, LensExtensionState> = {};
const state = this.extensions.get(extId); return Array.from(extensionLoader.userExtensions).reduce((state, [extId, ext]) => {
this.extensions.set(extId, { state[extId] = {
...(state || {}), enabled: ext.isEnabled,
enabled: enabled, }
return state;
}, state)
}
async manageState(extensionLoader: ExtensionLoader) {
await extensionLoader.whenLoaded;
await this.whenLoaded;
// activate user-extensions when state is ready
extensionLoader.userExtensions.forEach((ext, extId) => {
ext.isEnabled = this.isEnabled(extId);
});
// apply state on changes from store
reaction(() => this.state.toJS(), extensionsState => {
extensionsState.forEach((state, extId) => {
const ext = extensionLoader.getExtension(extId);
if (ext && !ext.isBundled) {
ext.isEnabled = state.enabled;
}
})
})
// save state on change `extension.isEnabled`
reaction(() => this.getState(extensionLoader), extensionsState => {
this.state.merge(extensionsState)
}) })
} }
isEnabled(extensionId: LensExtensionId) { isEnabled(extId: LensExtensionId) {
const state = this.extensions.get(extensionId); const state = this.state.get(extId);
return !state /* enabled by default */ || state.enabled; return !state /* enabled by default */ || state.enabled;
} }
@action
protected fromStore({ extensions }: LensExtensionsStoreModel) { protected fromStore({ extensions }: LensExtensionsStoreModel) {
this.extensions.merge(extensions); this.state.merge(extensions);
} }
toJSON(): LensExtensionsStoreModel { toJSON(): LensExtensionsStoreModel {
return toJS({ return toJS({
extensions: this.extensions.toJSON(), extensions: this.state.toJSON(),
}, { }, {
recurseEverything: true recurseEverything: true
}) })

View File

@ -4,6 +4,8 @@ import React from "react";
import * as Mobx from "mobx" import * as Mobx from "mobx"
import * as MobxReact from "mobx-react" import * as MobxReact from "mobx-react"
import * as LensExtensions from "../extensions/extension-api" import * as LensExtensions from "../extensions/extension-api"
import { App } from "./components/app";
import { LensApp } from "./lens-app";
import { render, unmountComponentAtNode } from "react-dom"; import { render, unmountComponentAtNode } from "react-dom";
import { isMac } from "../common/vars"; import { isMac } from "../common/vars";
import { userStore } from "../common/user-store"; import { userStore } from "../common/user-store";
@ -11,8 +13,6 @@ import { workspaceStore } from "../common/workspace-store";
import { clusterStore } from "../common/cluster-store"; import { clusterStore } from "../common/cluster-store";
import { i18nStore } from "./i18n"; import { i18nStore } from "./i18n";
import { themeStore } from "./theme.store"; import { themeStore } from "./theme.store";
import { App } from "./components/app";
import { LensApp } from "./lens-app";
import { extensionsStore } from "../extensions/extensions-store"; import { extensionsStore } from "../extensions/extensions-store";
type AppComponent = React.ComponentType & { type AppComponent = React.ComponentType & {

View File

@ -12,7 +12,6 @@ import { Icon } from "../icon";
import { PageLayout } from "../layout/page-layout"; import { PageLayout } from "../layout/page-layout";
import { extensionLoader } from "../../../extensions/extension-loader"; import { extensionLoader } from "../../../extensions/extension-loader";
import { extensionManager } from "../../../extensions/extension-manager"; import { extensionManager } from "../../../extensions/extension-manager";
import { extensionsStore } from "../../../extensions/extensions-store";
@observer @observer
export class Extensions extends React.Component { export class Extensions extends React.Component {
@ -20,7 +19,7 @@ export class Extensions extends React.Component {
@computed get extensions() { @computed get extensions() {
const searchText = this.search.toLowerCase(); const searchText = this.search.toLowerCase();
return extensionLoader.userExtensions.filter(ext => { return Array.from(extensionLoader.userExtensions.values()).filter(ext => {
const { name, description } = ext.manifest; const { name, description } = ext.manifest;
return [ return [
name.toLowerCase().includes(searchText), name.toLowerCase().includes(searchText),
@ -70,7 +69,7 @@ export class Extensions extends React.Component {
) )
} }
return extensions.map(ext => { return extensions.map(ext => {
const { manifestPath: extId, enabled, manifest } = ext; const { manifestPath: extId, isEnabled, manifest } = ext;
const { name, description } = manifest; const { name, description } = manifest;
return ( return (
<div key={extId} className="extension flex gaps align-center"> <div key={extId} className="extension flex gaps align-center">
@ -82,11 +81,11 @@ export class Extensions extends React.Component {
Description: <span className="text-secondary">{description}</span> Description: <span className="text-secondary">{description}</span>
</div> </div>
</div> </div>
{!enabled && ( {!isEnabled && (
<Button plain active onClick={() => extensionsStore.setEnabled(extId, true)}>Enable</Button> <Button plain active onClick={() => ext.isEnabled = true}>Enable</Button>
)} )}
{enabled && ( {isEnabled && (
<Button accent onClick={() => extensionsStore.setEnabled(extId, false)}>Disable</Button> <Button accent onClick={() => ext.isEnabled = false}>Disable</Button>
)} )}
</div> </div>
) )