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

Introduce competition for tray

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-05-11 08:16:04 +03:00
parent 44e0ebc10d
commit 35d6b54ed4
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
8 changed files with 266 additions and 13 deletions

View File

@ -46,7 +46,6 @@ import setupSentryInjectable from "./start-main-application/runnables/setup-sent
import setupShellInjectable from "./start-main-application/runnables/setup-shell.injectable";
import setupSyncingOfWeblinksInjectable from "./start-main-application/runnables/setup-syncing-of-weblinks.injectable";
import stopServicesAndExitAppInjectable from "./stop-services-and-exit-app.injectable";
import trayInjectable from "./tray/tray.injectable";
import applicationMenuInjectable from "./menu/application-menu.injectable";
import isDevelopmentInjectable from "../common/vars/is-development.injectable";
import setupSystemCaInjectable from "./start-main-application/runnables/setup-system-ca.injectable";
@ -81,6 +80,7 @@ import syncUpdateIsReadyToBeInstalledInjectable from "./electron-app/runnables/u
import quitAndInstallUpdateInjectable from "./electron-app/features/quit-and-install-update.injectable";
import electronUpdaterIsActiveInjectable from "./electron-app/features/electron-updater-is-active.injectable";
import publishIsConfiguredInjectable from "./update-app/publish-is-configured.injectable";
import checkForPlatformUpdatesInjectable from "./update-app/check-for-platform-updates.injectable";
export function getDiForUnitTesting(opts: GetDiForUnitTestingOptions = {}) {
const {
@ -125,7 +125,6 @@ export function getDiForUnitTesting(opts: GetDiForUnitTestingOptions = {}) {
di.override(stopServicesAndExitAppInjectable, () => () => {});
di.override(lensResourcesDirInjectable, () => "/irrelevant");
di.override(trayInjectable, () => ({ start: () => {}, stop: () => {} }));
di.override(applicationMenuInjectable, () => ({ start: () => {}, stop: () => {} }));
// TODO: Remove usages of globally exported appEventBus to get rid of this
@ -226,6 +225,10 @@ const overrideElectronFeatures = (di: DiContainer) => {
di.override(syncUpdateIsReadyToBeInstalledInjectable, () => ({ start: () => {}, stop: () => {} }));
di.override(quitAndInstallUpdateInjectable, () => () => {});
di.override(checkForPlatformUpdatesInjectable, () => () => {
throw new Error("Tried to check for platform updates without explicit override.");
});
di.override(createElectronWindowForInjectable, () => () => async () => ({
show: () => {},

View File

@ -0,0 +1,116 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { Menu, Tray } from "electron";
import packageJsonInjectable from "../../../common/vars/package-json.injectable";
import logger from "../../logger";
import { TRAY_LOG_PREFIX } from "../tray";
import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable";
import type { TrayMenuItem } from "../tray-menu-item/tray-menu-item-injection-token";
import { pipeline } from "@ogre-tools/fp";
import { isEmpty, map, filter } from "lodash/fp";
import isWindowsInjectable from "../../../common/vars/is-windows.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import trayIconPathInjectable from "../tray-icon-path.injectable";
const electronTrayInjectable = getInjectable({
id: "electron-tray",
instantiate: (di) => {
const packageJson = di.inject(packageJsonInjectable);
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
const isWindows = di.inject(isWindowsInjectable);
const logger = di.inject(loggerInjectable);
const trayIconPath = di.inject(trayIconPathInjectable);
let tray: Tray;
return {
start: () => {
tray = new Tray(trayIconPath);
tray.setToolTip(packageJson.description);
tray.setIgnoreDoubleClickEvents(true);
if (isWindows) {
tray.on("click", () => {
showApplicationWindow()
.catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to open lens`, { error }));
});
}
},
stop: () => {
tray.destroy();
},
setMenuItems: (items: TrayMenuItem[]) => {
pipeline(
items,
convertToElectronMenuTemplate,
Menu.buildFromTemplate,
(template) => {
tray.setContextMenu(template);
},
);
},
};
},
causesSideEffects: true,
});
export default electronTrayInjectable;
const convertToElectronMenuTemplate = (trayMenuItems: TrayMenuItem[]) => {
const _toTrayMenuOptions = (parentId: string | null) =>
pipeline(
trayMenuItems,
filter((item) => item.parentId === parentId),
map(
(trayMenuItem: TrayMenuItem): Electron.MenuItemConstructorOptions => {
if (trayMenuItem.separator) {
return { id: trayMenuItem.id, type: "separator" };
}
const childItems = _toTrayMenuOptions(trayMenuItem.id);
return {
id: trayMenuItem.id,
label: trayMenuItem.label.get(),
enabled: trayMenuItem.enabled.get(),
toolTip: trayMenuItem.tooltip,
...(isEmpty(childItems)
? {
type: "normal",
submenu: _toTrayMenuOptions(trayMenuItem.id),
click: () => {
try {
trayMenuItem.click?.();
} catch (error) {
logger.error(
`${TRAY_LOG_PREFIX}: clicking item "${trayMenuItem.id} failed."`,
{ error },
);
}
},
}
: {
type: "submenu",
submenu: _toTrayMenuOptions(trayMenuItem.id),
}),
};
},
),
);
return _toTrayMenuOptions(null);
};

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import electronTrayInjectable from "./electron-tray.injectable";
const startTrayInjectable = getInjectable({
id: "start-tray",
instantiate: (di) => {
const electronTray = di.inject(electronTrayInjectable);
return {
run: () => {
electronTray.start();
},
};
},
injectionToken: onLoadOfApplicationInjectionToken,
});
export default startTrayInjectable;

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import electronTrayInjectable from "./electron-tray.injectable";
import { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
import stopReactiveTrayMenuItemsInjectable from "../reactive-tray-menu-items/stop-reactive-tray-menu-items.injectable";
const stopTrayInjectable = getInjectable({
id: "stop-tray",
instantiate: (di) => {
const electronTray = di.inject(electronTrayInjectable);
return {
run: () => {
electronTray.stop();
},
runAfter: di.inject(stopReactiveTrayMenuItemsInjectable),
};
},
injectionToken: beforeQuitOfBackEndInjectionToken,
});
export default stopTrayInjectable;

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { getStartableStoppable } from "../../../common/utils/get-startable-stoppable";
import { autorun } from "mobx";
import trayMenuItemsInjectable from "../tray-menu-item/tray-menu-items.injectable";
import electronTrayInjectable from "../electron-tray/electron-tray.injectable";
const reactiveTrayMenuItemsInjectable = getInjectable({
id: "reactive-tray-menu-items",
instantiate: (di) => {
const electronTray = di.inject(electronTrayInjectable);
const trayMenuItems = di.inject(trayMenuItemsInjectable);
return getStartableStoppable("reactive-tray-menu-items", () => autorun(() => {
electronTray.setMenuItems(trayMenuItems.get());
}));
},
});
export default reactiveTrayMenuItemsInjectable;

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import reactiveTrayMenuItemsInjectable from "./reactive-tray-menu-items.injectable";
import { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import startTrayInjectable from "../electron-tray/start-tray.injectable";
const startReactiveTrayMenuItemsInjectable = getInjectable({
id: "start-reactive-tray-menu-items",
instantiate: (di) => {
const reactiveTrayMenuItems = di.inject(reactiveTrayMenuItemsInjectable);
return {
run: async () => {
await reactiveTrayMenuItems.start();
},
runAfter: di.inject(startTrayInjectable),
};
},
injectionToken: onLoadOfApplicationInjectionToken,
});
export default startReactiveTrayMenuItemsInjectable;

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import reactiveTrayMenuItemsInjectable from "./reactive-tray-menu-items.injectable";
import { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
const stopReactiveTrayMenuItemsInjectable = getInjectable({
id: "stop-reactive-tray-menu-items",
instantiate: (di) => {
const reactiveTrayMenuItems = di.inject(reactiveTrayMenuItemsInjectable);
return {
run: async () => {
await reactiveTrayMenuItems.stop();
},
};
},
injectionToken: beforeQuitOfBackEndInjectionToken,
});
export default stopReactiveTrayMenuItemsInjectable;

View File

@ -47,6 +47,7 @@ import historyInjectable from "../../navigation/history.injectable";
import trayMenuItemsInjectable from "../../../main/tray/tray-menu-item/tray-menu-items.injectable";
import type { TrayMenuItem } from "../../../main/tray/tray-menu-item/tray-menu-item-injection-token";
import updateIsAvailableStateInjectable from "../../../main/update-app/update-is-ready-to-be-installed-state.injectable";
import electronTrayInjectable from "../../../main/tray/electron-tray/electron-tray.injectable";
type Callback = (dis: DiContainers) => void | Promise<void>;
@ -150,6 +151,17 @@ export const getApplicationBuilder = () => {
computed(() => []),
);
let trayMenuItemsStateFake: TrayMenuItem[];
mainDi.override(electronTrayInjectable, () => ({
start: () => {},
stop: () => {},
setMenuItems: (items) => {
trayMenuItemsStateFake = items;
},
}));
let allowedResourcesState: IObservableArray<KubeResource>;
let rendered: RenderResult;
@ -202,26 +214,18 @@ export const getApplicationBuilder = () => {
tray: {
get: (id: string) => {
const trayMenuItems = mainDi.inject(
trayMenuItemsInjectable,
);
return trayMenuItems.get().find(matches({ id }));
return trayMenuItemsStateFake.find(matches({ id }));
},
click: async (id: string) => {
const trayMenuItems = mainDi.inject(
trayMenuItemsInjectable,
);
const menuItem = pipeline(
trayMenuItems.get(),
trayMenuItemsStateFake,
find((menuItem) => menuItem.id === id),
);
if (!menuItem) {
const availableIds = pipeline(
trayMenuItems.get(),
trayMenuItemsStateFake,
filter(item => !!item.click),
map(item => item.id),
join(", "),