diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts index bbbaba7c5c..fc918476fb 100644 --- a/src/extensions/extension-loader.ts +++ b/src/extensions/extension-loader.ts @@ -16,46 +16,34 @@ export function extensionPackagesRoot() { } export class ExtensionLoader { - @observable isLoaded = false; protected extensions = observable.map(); - protected instances = observable.map() + protected instances = observable.map(); + + @observable isLoaded = false; + whenLoaded = when(() => this.isLoaded); constructor() { if (ipcRenderer) { - ipcRenderer.on("extensions:loaded", (event, extensions: InstalledExtension[]) => { + ipcRenderer.on("extensions:loaded", (event, extensions: [LensExtensionId, InstalledExtension][]) => { this.isLoaded = true; - extensions.forEach((ext) => { - if (!this.extensions.has(ext.manifestPath)) { - this.extensions.set(ext.manifestPath, ext) + extensions.forEach(([extId, ext]) => { + if (!this.extensions.has(extId)) { + this.extensions.set(extId, ext) } }) }); } - this.manageExtensionsState(); + extensionsStore.manageState(this); } - @computed get userExtensions(): InstalledExtension[] { - return Array.from(this.toJSON().values()).filter(ext => !ext.isBundled) - } - - protected async manageExtensionsState() { - await extensionsStore.whenLoaded; - await when(() => this.isLoaded); - - // apply initial state - this.extensions.forEach((ext, extId) => { - ext.enabled = ext.isBundled || extensionsStore.isEnabled(extId); + @computed get userExtensions(): Map { + const extensions = this.extensions.toJS(); + extensions.forEach((ext, extId) => { + if (ext.isBundled) { + extensions.delete(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 @@ -93,14 +81,13 @@ export class ExtensionLoader { } protected autoInitExtensions(register: (ext: LensExtension) => Function[]) { - return reaction(() => this.toJSON(), (installedExtensions) => { + return reaction(() => this.toJSON(), installedExtensions => { for (const [extId, ext] of installedExtensions) { let instance = this.instances.get(extId); - if (ext.enabled && !instance) { + if (ext.isEnabled && !instance) { try { - const extensionModule = this.requireExtension(ext) - if (!extensionModule) continue; - const LensExtensionClass: LensExtensionConstructor = extensionModule.default; + const LensExtensionClass: LensExtensionConstructor = this.requireExtension(ext) + if (!LensExtensionClass) continue; instance = new LensExtensionClass(ext); instance.whenEnabled(() => register(instance)); instance.enable(); @@ -108,7 +95,7 @@ export class ExtensionLoader { } catch (err) { logger.error(`[EXTENSION-LOADER]: activation extension error`, { ext, err }) } - } else if (!ext.enabled && instance) { + } else if (!ext.isEnabled && instance) { try { instance.disable(); this.instances.delete(extId); @@ -131,7 +118,7 @@ export class ExtensionLoader { extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main)) } if (extEntrypoint !== "") { - return __non_webpack_require__(extEntrypoint) + return __non_webpack_require__(extEntrypoint).default; } } catch (err) { 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 { return toJS(this.extensions, { exportMapsAsObjects: false, recurseEverything: true, @@ -153,7 +144,7 @@ export class ExtensionLoader { frameId: frameId, frameOnly: !!frameId, args: [ - Array.from(this.toJSON().values()), + Array.from(this.toJSON()), ], }) } diff --git a/src/extensions/extension-manager.ts b/src/extensions/extension-manager.ts index f538028adf..d45bf75f53 100644 --- a/src/extensions/extension-manager.ts +++ b/src/extensions/extension-manager.ts @@ -10,8 +10,8 @@ import { getBundledExtensions } from "../common/utils/app-version" export interface InstalledExtension { readonly manifest: LensExtensionManifest; readonly manifestPath: string; - readonly isBundled?: boolean; // defined in package.json - enabled?: boolean; + readonly isBundled?: boolean; // defined in project root's package.json + isEnabled: boolean; } type Dependencies = { @@ -90,6 +90,7 @@ export class ExtensionManager { manifestPath: path.join(this.nodeModulesPath, manifestJson.name, "package.json"), manifest: manifestJson, isBundled: isBundled, + isEnabled: isBundled, } } catch (err) { logger.error(`[EXTENSION-MANAGER]: can't install extension at ${manifestPath}: ${err}`, { manifestJson }); diff --git a/src/extensions/extensions-store.ts b/src/extensions/extensions-store.ts index 86eb605c52..ba942c41d9 100644 --- a/src/extensions/extensions-store.ts +++ b/src/extensions/extensions-store.ts @@ -1,6 +1,7 @@ import type { LensExtensionId } from "./lens-extension"; +import type { ExtensionLoader } from "./extension-loader"; import { BaseStore } from "../common/base-store" -import { action, observable, toJS } from "mobx"; +import { action, observable, reaction, toJS } from "mobx"; export interface LensExtensionsStoreModel { extensions: Record; @@ -13,33 +14,60 @@ export interface LensExtensionState { export class ExtensionsStore extends BaseStore { constructor() { super({ - configName: "lens-extensions" + configName: "lens-extensions", }); } - @observable extensions = observable.map(); + protected state = observable.map(); - @action - setEnabled(extId: LensExtensionId, enabled: boolean) { - const state = this.extensions.get(extId); - this.extensions.set(extId, { - ...(state || {}), - enabled: enabled, + protected getState(extensionLoader: ExtensionLoader) { + const state: Record = {}; + return Array.from(extensionLoader.userExtensions).reduce((state, [extId, ext]) => { + state[extId] = { + enabled: ext.isEnabled, + } + 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) { - const state = this.extensions.get(extensionId); + isEnabled(extId: LensExtensionId) { + const state = this.state.get(extId); return !state /* enabled by default */ || state.enabled; } + @action protected fromStore({ extensions }: LensExtensionsStoreModel) { - this.extensions.merge(extensions); + this.state.merge(extensions); } toJSON(): LensExtensionsStoreModel { return toJS({ - extensions: this.extensions.toJSON(), + extensions: this.state.toJSON(), }, { recurseEverything: true }) diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index ca27086f4a..52a92b7c11 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -4,6 +4,8 @@ import React from "react"; import * as Mobx from "mobx" import * as MobxReact from "mobx-react" import * as LensExtensions from "../extensions/extension-api" +import { App } from "./components/app"; +import { LensApp } from "./lens-app"; import { render, unmountComponentAtNode } from "react-dom"; import { isMac } from "../common/vars"; import { userStore } from "../common/user-store"; @@ -11,8 +13,6 @@ import { workspaceStore } from "../common/workspace-store"; import { clusterStore } from "../common/cluster-store"; import { i18nStore } from "./i18n"; import { themeStore } from "./theme.store"; -import { App } from "./components/app"; -import { LensApp } from "./lens-app"; import { extensionsStore } from "../extensions/extensions-store"; type AppComponent = React.ComponentType & { diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index f6b1c4399e..34814393ae 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -12,7 +12,6 @@ import { Icon } from "../icon"; import { PageLayout } from "../layout/page-layout"; import { extensionLoader } from "../../../extensions/extension-loader"; import { extensionManager } from "../../../extensions/extension-manager"; -import { extensionsStore } from "../../../extensions/extensions-store"; @observer export class Extensions extends React.Component { @@ -20,7 +19,7 @@ export class Extensions extends React.Component { @computed get extensions() { const searchText = this.search.toLowerCase(); - return extensionLoader.userExtensions.filter(ext => { + return Array.from(extensionLoader.userExtensions.values()).filter(ext => { const { name, description } = ext.manifest; return [ name.toLowerCase().includes(searchText), @@ -70,7 +69,7 @@ export class Extensions extends React.Component { ) } return extensions.map(ext => { - const { manifestPath: extId, enabled, manifest } = ext; + const { manifestPath: extId, isEnabled, manifest } = ext; const { name, description } = manifest; return (
@@ -82,11 +81,11 @@ export class Extensions extends React.Component { Description: {description}
- {!enabled && ( - + {!isEnabled && ( + )} - {enabled && ( - + {isEnabled && ( + )} )