mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Make registrator for application menu items support all known scenarios
Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
parent
9f19fdceb2
commit
66e4ec1f53
@ -3,6 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { noop } from "lodash/fp";
|
||||||
import { action } from "mobx";
|
import { action } from "mobx";
|
||||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
@ -40,6 +41,14 @@ describe("application-menu-in-legacy-extension-api", () => {
|
|||||||
|
|
||||||
mainOptions: {
|
mainOptions: {
|
||||||
appMenus: [
|
appMenus: [
|
||||||
|
{
|
||||||
|
id: "some-non-shown-item",
|
||||||
|
parentId: "some-top-menu-item",
|
||||||
|
click: noop,
|
||||||
|
label: "Irrelevant",
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "some-clickable-item",
|
id: "some-clickable-item",
|
||||||
parentId: "some-top-menu-item",
|
parentId: "some-top-menu-item",
|
||||||
@ -50,6 +59,21 @@ describe("application-menu-in-legacy-extension-api", () => {
|
|||||||
parentId: "some-top-menu-item",
|
parentId: "some-top-menu-item",
|
||||||
type: "separator",
|
type: "separator",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "some-os-action-menu-item-id",
|
||||||
|
parentId: "some-top-menu-item",
|
||||||
|
role: "help",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "some-submenu-with-explicit-children",
|
||||||
|
parentId: "some-top-menu-item",
|
||||||
|
|
||||||
|
submenu: [
|
||||||
|
{ id: "some-explicit-child", label: "Some explicit child", click: noop },
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -57,21 +81,25 @@ describe("application-menu-in-legacy-extension-api", () => {
|
|||||||
builder.extensions.enable(testExtensionOptions);
|
builder.extensions.enable(testExtensionOptions);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("menu related items exist", () => {
|
it("related menu items exist", () => {
|
||||||
const menuItemPathsForExtension = builder.applicationMenu.items.filter(
|
const menuItemPathsForExtension = builder.applicationMenu.items.filter(
|
||||||
(x) =>
|
(x) =>
|
||||||
x.startsWith("root.some-top-menu-item.some-extension-name"),
|
x.startsWith("root.some-top-menu-item.some-extension-name"),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(menuItemPathsForExtension).toEqual([
|
expect(menuItemPathsForExtension).toEqual([
|
||||||
"root.some-top-menu-item.some-extension-name/application-menu-item/clickable-menu-item(some-clickable-item)",
|
"root.some-top-menu-item.some-extension-name/some-clickable-item",
|
||||||
"root.some-top-menu-item.some-extension-name/application-menu-item/separator(1)",
|
// Note: anonymous index "1" is used by the non-visible menu item.
|
||||||
|
"root.some-top-menu-item.some-extension-name/2-separator",
|
||||||
|
"root.some-top-menu-item.some-extension-name/some-os-action-menu-item-id",
|
||||||
|
"root.some-top-menu-item.some-extension-name/some-submenu-with-explicit-children",
|
||||||
|
"root.some-top-menu-item.some-extension-name/some-submenu-with-explicit-children.some-extension-name/some-submenu-with-explicit-children/some-explicit-child",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when the extension-based clickable menu item is clicked, does so", () => {
|
it("when the extension-based clickable menu item is clicked, does so", () => {
|
||||||
builder.applicationMenu.click(
|
builder.applicationMenu.click(
|
||||||
"root.some-top-menu-item.some-extension-name/application-menu-item/clickable-menu-item(some-clickable-item)",
|
"root.some-top-menu-item.some-extension-name/some-clickable-item",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(onClickMock).toHaveBeenCalled();
|
expect(onClickMock).toHaveBeenCalled();
|
||||||
@ -100,8 +128,11 @@ describe("application-menu-in-legacy-extension-api", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(menuItemPathsForExtension).toEqual([
|
expect(menuItemPathsForExtension).toEqual([
|
||||||
"root.some-top-menu-item.some-extension-name/application-menu-item/clickable-menu-item(some-clickable-item)",
|
"root.some-top-menu-item.some-extension-name/some-clickable-item",
|
||||||
"root.some-top-menu-item.some-extension-name/application-menu-item/separator(1)",
|
"root.some-top-menu-item.some-extension-name/2-separator",
|
||||||
|
"root.some-top-menu-item.some-extension-name/some-os-action-menu-item-id",
|
||||||
|
"root.some-top-menu-item.some-extension-name/some-submenu-with-explicit-children",
|
||||||
|
"root.some-top-menu-item.some-extension-name/some-submenu-with-explicit-children.some-extension-name/some-submenu-with-explicit-children/some-explicit-child",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,12 +2,19 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
import type { Injectable } from "@ogre-tools/injectable";
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token";
|
import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token";
|
||||||
import type { LensExtension } from "../../../extensions/lens-extension";
|
import type { LensExtension } from "../../../extensions/lens-extension";
|
||||||
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
|
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
|
||||||
import type { ClickableMenuItem, Separator } from "./menu-items/application-menu-item-injection-token";
|
import type {
|
||||||
|
ApplicationMenuItemTypes,
|
||||||
|
ClickableMenuItem,
|
||||||
|
OsActionMenuItem,
|
||||||
|
Separator,
|
||||||
|
} from "./menu-items/application-menu-item-injection-token";
|
||||||
import applicationMenuItemInjectionToken from "./menu-items/application-menu-item-injection-token";
|
import applicationMenuItemInjectionToken from "./menu-items/application-menu-item-injection-token";
|
||||||
|
import type { MenuRegistration } from "./menu-registration";
|
||||||
|
|
||||||
const applicationMenuItemRegistratorInjectable = getInjectable({
|
const applicationMenuItemRegistratorInjectable = getInjectable({
|
||||||
id: "application-menu-item-registrator",
|
id: "application-menu-item-registrator",
|
||||||
@ -15,44 +22,122 @@ const applicationMenuItemRegistratorInjectable = getInjectable({
|
|||||||
instantiate: () => (ext: LensExtension) => {
|
instantiate: () => (ext: LensExtension) => {
|
||||||
const extension = ext as LensMainExtension;
|
const extension = ext as LensMainExtension;
|
||||||
|
|
||||||
return extension.appMenus.map((registration, index) => {
|
return extension.appMenus.flatMap(
|
||||||
const registrationId = registration.id || index;
|
toRecursedInjectables([extension.sanitizedExtensionId]),
|
||||||
const applicationMenuId = `${extension.sanitizedExtensionId}/application-menu-item`;
|
);
|
||||||
|
|
||||||
return getInjectable({
|
|
||||||
id: `${applicationMenuId}/${registrationId}`,
|
|
||||||
|
|
||||||
instantiate: () => {
|
|
||||||
const orderNumber = 1000 + index * 10;
|
|
||||||
|
|
||||||
if (registration.type === "separator") {
|
|
||||||
return {
|
|
||||||
kind: "separator" as const,
|
|
||||||
id: `${applicationMenuId}/separator(${registrationId})`,
|
|
||||||
parentId: registration.parentId,
|
|
||||||
orderNumber,
|
|
||||||
} as Separator;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
kind: "clickable-menu-item" as const,
|
|
||||||
id: `${applicationMenuId}/clickable-menu-item(${registrationId})`,
|
|
||||||
parentId: registration.parentId,
|
|
||||||
// Todo: hide electron evens from this abstraction.
|
|
||||||
onClick: registration.click,
|
|
||||||
label: registration.label,
|
|
||||||
isShown: registration.visible ?? true,
|
|
||||||
orderNumber,
|
|
||||||
...(registration.accelerator ? { keyboardShortcut: registration.accelerator as string } : {}),
|
|
||||||
} as ClickableMenuItem;
|
|
||||||
},
|
|
||||||
|
|
||||||
injectionToken: applicationMenuItemInjectionToken,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
injectionToken: extensionRegistratorInjectionToken,
|
injectionToken: extensionRegistratorInjectionToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default applicationMenuItemRegistratorInjectable;
|
export default applicationMenuItemRegistratorInjectable;
|
||||||
|
|
||||||
|
const toRecursedInjectables =
|
||||||
|
(previousIdPath: string[]) =>
|
||||||
|
(
|
||||||
|
registration: MenuRegistration,
|
||||||
|
index: number,
|
||||||
|
// Todo: new version of injectable would require less type parameters with defaults.
|
||||||
|
): Injectable<ApplicationMenuItemTypes, ApplicationMenuItemTypes, void>[] => {
|
||||||
|
const previousIdPathString = previousIdPath.join("/");
|
||||||
|
const registrationId = registration.id || index.toString();
|
||||||
|
const currentIdPath = [...previousIdPath, registrationId];
|
||||||
|
const currentIdPathString = currentIdPath.join("/");
|
||||||
|
const parentId = registration.parentId || previousIdPathString;
|
||||||
|
|
||||||
|
const menuItem = getApplicationMenuItem({
|
||||||
|
registration,
|
||||||
|
parentId,
|
||||||
|
currentIdPathString,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!menuItem) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
getInjectable({
|
||||||
|
id: `${currentIdPathString}/application-menu-item`,
|
||||||
|
|
||||||
|
instantiate: () => menuItem,
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
}),
|
||||||
|
|
||||||
|
...((registration.submenu as MenuRegistration[])
|
||||||
|
? (registration.submenu as MenuRegistration[]).flatMap(
|
||||||
|
toRecursedInjectables(currentIdPath),
|
||||||
|
)
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getApplicationMenuItem = ({
|
||||||
|
registration,
|
||||||
|
index,
|
||||||
|
currentIdPathString,
|
||||||
|
parentId,
|
||||||
|
}: {
|
||||||
|
registration: MenuRegistration;
|
||||||
|
index: number;
|
||||||
|
currentIdPathString: string;
|
||||||
|
parentId: string;
|
||||||
|
}): ApplicationMenuItemTypes | undefined => {
|
||||||
|
const orderNumber = 1000 + index * 10;
|
||||||
|
|
||||||
|
if (registration.type === "separator") {
|
||||||
|
return {
|
||||||
|
kind: "separator" as const,
|
||||||
|
id: `${currentIdPathString}-separator`,
|
||||||
|
parentId,
|
||||||
|
orderNumber,
|
||||||
|
} as Separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registration.submenu) {
|
||||||
|
return {
|
||||||
|
kind: "sub-menu" as const,
|
||||||
|
id: currentIdPathString,
|
||||||
|
parentId,
|
||||||
|
isShown: registration.visible ?? true,
|
||||||
|
orderNumber,
|
||||||
|
label: registration.label || "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registration.click) {
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
id: currentIdPathString,
|
||||||
|
parentId,
|
||||||
|
// Todo: hide electron events from this abstraction.
|
||||||
|
onClick: registration.click,
|
||||||
|
label: registration.label,
|
||||||
|
isShown: registration.visible ?? true,
|
||||||
|
orderNumber,
|
||||||
|
|
||||||
|
...(registration.accelerator
|
||||||
|
? { keyboardShortcut: registration.accelerator as string }
|
||||||
|
: {}),
|
||||||
|
} as ClickableMenuItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registration.role) {
|
||||||
|
return {
|
||||||
|
kind: "os-action-menu-item" as const,
|
||||||
|
id: currentIdPathString,
|
||||||
|
parentId,
|
||||||
|
label: registration.label,
|
||||||
|
isShown: registration.visible ?? true,
|
||||||
|
orderNumber,
|
||||||
|
actionName: registration.role,
|
||||||
|
|
||||||
|
...(registration.accelerator
|
||||||
|
? { keyboardShortcut: registration.accelerator as string }
|
||||||
|
: {}),
|
||||||
|
} as OsActionMenuItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user