mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Allow computed tray menu for extensions (#6598)
* Add computed tray menu for extensions. Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com> * Use disposer. Fix style. Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com> * Register/reregister injectables by id. Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com> Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com>
This commit is contained in:
parent
a91e3a7f8e
commit
1861fe2049
@ -4,9 +4,11 @@
|
||||
*/
|
||||
import type { Injectable } from "@ogre-tools/injectable";
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import type { LensExtension } from "../lens-extension";
|
||||
|
||||
export type ExtensionRegistrator = (extension: LensExtension) => Injectable<any, any, any>[];
|
||||
export type ExtensionRegistrator = (extension: LensExtension) =>
|
||||
Injectable<any, any, any>[] | IComputedValue<Injectable<any, any, any>[]>;
|
||||
|
||||
export const extensionRegistratorInjectionToken = getInjectionToken<ExtensionRegistrator>({
|
||||
id: "extension-registrator-token",
|
||||
|
||||
@ -2,8 +2,11 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Injectable } from "@ogre-tools/injectable";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { runInAction } from "mobx";
|
||||
import { difference, find, map } from "lodash";
|
||||
import { reaction, runInAction } from "mobx";
|
||||
import { disposer } from "../../../common/utils/disposer";
|
||||
import type { LensExtension } from "../../lens-extension";
|
||||
import { extensionRegistratorInjectionToken } from "../extension-registrator-injection-token";
|
||||
|
||||
@ -12,6 +15,16 @@ export interface Extension {
|
||||
deregister: () => void;
|
||||
}
|
||||
|
||||
const idsToInjectables = (ids: string[], injectables: Injectable<any, any, any>[]) => ids.map(id => {
|
||||
const injectable = find(injectables, { id });
|
||||
|
||||
if (!injectable) {
|
||||
throw new Error(`Injectable ${id} not found`);
|
||||
}
|
||||
|
||||
return injectable;
|
||||
});
|
||||
|
||||
const extensionInjectable = getInjectable({
|
||||
id: "extension",
|
||||
|
||||
@ -21,19 +34,42 @@ const extensionInjectable = getInjectable({
|
||||
|
||||
instantiate: (childDi) => {
|
||||
const extensionRegistrators = childDi.injectMany(extensionRegistratorInjectionToken);
|
||||
const reactionDisposer = disposer();
|
||||
|
||||
return {
|
||||
register: () => {
|
||||
const injectables = extensionRegistrators.flatMap((getInjectablesOfExtension) =>
|
||||
getInjectablesOfExtension(instance),
|
||||
);
|
||||
extensionRegistrators.forEach((getInjectablesOfExtension) => {
|
||||
const injectables = getInjectablesOfExtension(instance);
|
||||
|
||||
runInAction(() => {
|
||||
childDi.register(...injectables);
|
||||
reactionDisposer.push(
|
||||
// injectables is either an array or a computed array, in which case
|
||||
// we need to update the registered injectables with a reaction every time they change
|
||||
reaction(
|
||||
() => Array.isArray(injectables) ? injectables : injectables.get(),
|
||||
(currentInjectables, previousInjectables = []) => {
|
||||
// Register new injectables and deregister removed injectables by id
|
||||
const currentIds = map(currentInjectables, "id");
|
||||
const previousIds = map(previousInjectables, "id");
|
||||
const idsToAdd = difference(currentIds, previousIds);
|
||||
const idsToRemove = previousIds.filter(previousId => !currentIds.includes(previousId));
|
||||
|
||||
if (idsToRemove.length > 0) {
|
||||
childDi.deregister(...idsToInjectables(idsToRemove, previousInjectables));
|
||||
}
|
||||
|
||||
if (idsToAdd.length > 0) {
|
||||
childDi.register(...idsToInjectables(idsToAdd, currentInjectables));
|
||||
}
|
||||
}, {
|
||||
fireImmediately: true,
|
||||
},
|
||||
));
|
||||
});
|
||||
},
|
||||
|
||||
deregister: () => {
|
||||
reactionDisposer();
|
||||
|
||||
runInAction(() => {
|
||||
parentDi.deregister(extensionInjectable);
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
import { LensExtension, lensExtensionDependencies } from "./lens-extension";
|
||||
import type { CatalogEntity } from "../common/catalog";
|
||||
import type { IObservableArray } from "mobx";
|
||||
import type { IComputedValue, IObservableArray } from "mobx";
|
||||
import type { MenuRegistration } from "../features/application-menu/main/menu-registration";
|
||||
import type { TrayMenuRegistration } from "../main/tray/tray-menu-registration";
|
||||
import type { ShellEnvModifier } from "../main/shell-session/shell-env-modifier/shell-env-modifier-registration";
|
||||
@ -13,7 +13,7 @@ import type { LensMainExtensionDependencies } from "./lens-extension-set-depende
|
||||
|
||||
export class LensMainExtension extends LensExtension<LensMainExtensionDependencies> {
|
||||
appMenus: MenuRegistration[] = [];
|
||||
trayMenus: TrayMenuRegistration[] = [];
|
||||
trayMenus: TrayMenuRegistration[] | IComputedValue<TrayMenuRegistration[]> = [];
|
||||
|
||||
/**
|
||||
* implement this to modify the shell environment that Lens terminals are opened with. The ShellEnvModifier type has the signature
|
||||
|
||||
@ -2,13 +2,14 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { IObservableValue } from "mobx";
|
||||
import type { IObservableArray, IObservableValue } from "mobx";
|
||||
import { computed, runInAction, observable } from "mobx";
|
||||
import type { TrayMenuRegistration } from "../../main/tray/tray-menu-registration";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
|
||||
describe("preferences: extension adding tray items", () => {
|
||||
describe("when extension with tray items is enabled", () => {
|
||||
describe("when extension with tray items are statically defined", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
let someObservableForVisibility: IObservableValue<boolean>;
|
||||
let someObservableForEnabled: IObservableValue<boolean>;
|
||||
@ -190,4 +191,88 @@ describe("preferences: extension adding tray items", () => {
|
||||
expect(item?.enabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when extension with tray items are dynamically defined", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
let menuItems: IObservableArray<TrayMenuRegistration>;
|
||||
|
||||
beforeEach(async () => {
|
||||
builder = getApplicationBuilder();
|
||||
|
||||
await builder.render();
|
||||
|
||||
builder.preferences.navigate();
|
||||
|
||||
menuItems = observable.array([
|
||||
{
|
||||
id: "some-visible",
|
||||
label: "some-visible",
|
||||
click: () => {},
|
||||
visible: computed(() => true),
|
||||
},
|
||||
]);
|
||||
|
||||
const computedTrayMenu = computed(() => menuItems);
|
||||
|
||||
const testExtension = {
|
||||
id: "some-extension-id",
|
||||
name: "some-extension",
|
||||
|
||||
mainOptions: {
|
||||
trayMenus: computedTrayMenu,
|
||||
},
|
||||
};
|
||||
|
||||
builder.extensions.enable(testExtension);
|
||||
});
|
||||
|
||||
it("given item exists, it's shown", () => {
|
||||
expect(
|
||||
builder.tray.get(
|
||||
"some-visible-tray-menu-item-for-extension-some-extension",
|
||||
),
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it("given item is added, it's shown", async () => {
|
||||
menuItems.push({
|
||||
id: "some-added",
|
||||
label: "some-added",
|
||||
click: () => {},
|
||||
visible: computed(() => true),
|
||||
});
|
||||
|
||||
expect(
|
||||
builder.tray.get(
|
||||
"some-added-tray-menu-item-for-extension-some-extension",
|
||||
),
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it("given item is removed, it's not shown", async () => {
|
||||
menuItems.replace([]);
|
||||
|
||||
expect(
|
||||
builder.tray.get(
|
||||
"some-visible-tray-menu-item-for-extension-some-extension",
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("given items are removed and one added, it's shown", async () => {
|
||||
menuItems.replace([]);
|
||||
menuItems.push({
|
||||
id: "some-added",
|
||||
label: "some-added",
|
||||
click: () => {},
|
||||
visible: computed(() => true),
|
||||
});
|
||||
|
||||
expect(
|
||||
builder.tray.get(
|
||||
"some-added-tray-menu-item-for-extension-some-extension",
|
||||
),
|
||||
).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -15,8 +15,8 @@ const startReactiveTrayMenuItemsInjectable = getInjectable({
|
||||
|
||||
return {
|
||||
id: "start-reactive-tray-menu-items",
|
||||
run: async () => {
|
||||
await reactiveTrayMenuItems.start();
|
||||
run: () => {
|
||||
reactiveTrayMenuItems.start();
|
||||
},
|
||||
|
||||
runAfter: di.inject(startTrayInjectable),
|
||||
|
||||
@ -25,9 +25,13 @@ const trayMenuItemRegistratorInjectable = getInjectable({
|
||||
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
||||
const getRandomId = di.inject(getRandomIdInjectable);
|
||||
|
||||
return mainExtension.trayMenus.flatMap(
|
||||
return computed(() => {
|
||||
const trayMenus = Array.isArray(mainExtension.trayMenus) ? mainExtension.trayMenus : mainExtension.trayMenus.get();
|
||||
|
||||
return trayMenus.flatMap(
|
||||
toItemInjectablesFor(mainExtension, withErrorLoggingFor, getRandomId),
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
injectionToken: extensionRegistratorInjectionToken,
|
||||
@ -117,5 +121,3 @@ const toItemInjectablesFor = (extension: LensMainExtension, withErrorLoggingFor:
|
||||
|
||||
return _toItemInjectables(null);
|
||||
};
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user