1
0
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:
Iku-turso 2022-10-12 14:27:01 +03:00 committed by Janne Savolainen
parent 9f19fdceb2
commit 66e4ec1f53
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
2 changed files with 158 additions and 42 deletions

View File

@ -3,6 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { noop } from "lodash/fp";
import { action } from "mobx";
import type { ApplicationBuilder } 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: {
appMenus: [
{
id: "some-non-shown-item",
parentId: "some-top-menu-item",
click: noop,
label: "Irrelevant",
visible: false,
},
{
id: "some-clickable-item",
parentId: "some-top-menu-item",
@ -50,6 +59,21 @@ describe("application-menu-in-legacy-extension-api", () => {
parentId: "some-top-menu-item",
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);
});
it("menu related items exist", () => {
it("related menu items exist", () => {
const menuItemPathsForExtension = builder.applicationMenu.items.filter(
(x) =>
x.startsWith("root.some-top-menu-item.some-extension-name"),
);
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/application-menu-item/separator(1)",
"root.some-top-menu-item.some-extension-name/some-clickable-item",
// 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", () => {
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();
@ -100,8 +128,11 @@ describe("application-menu-in-legacy-extension-api", () => {
);
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/application-menu-item/separator(1)",
"root.some-top-menu-item.some-extension-name/some-clickable-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",
]);
});
});

View File

@ -2,12 +2,19 @@
* 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 } from "@ogre-tools/injectable";
import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token";
import type { LensExtension } from "../../../extensions/lens-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 type { MenuRegistration } from "./menu-registration";
const applicationMenuItemRegistratorInjectable = getInjectable({
id: "application-menu-item-registrator",
@ -15,44 +22,122 @@ const applicationMenuItemRegistratorInjectable = getInjectable({
instantiate: () => (ext: LensExtension) => {
const extension = ext as LensMainExtension;
return extension.appMenus.map((registration, index) => {
const registrationId = registration.id || index;
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,
});
});
return extension.appMenus.flatMap(
toRecursedInjectables([extension.sanitizedExtensionId]),
);
},
injectionToken: extensionRegistratorInjectionToken,
});
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;
};