mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Introduce way to install update directly from tray. Make application updater unit testable... (#5433)
* Extract product name as injectable Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make tray items comply with Open Closed Principle Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Replace duplicated overrides with global Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add behaviour for navigating to preferences using tray Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce a tray item for updating application Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak naming Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak more naming Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant indirection Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak more naming Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce injectable for package.json being side-effect Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Relocate file to directory containing feature Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using injectable for limiting side effect Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing injection token for implementation of tray item Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove resetting state for update is ready to be installed for being unclear Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make label of tray item reactive Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract updating is enabled to separate injectable Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for tray Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Expand scope of behaviour for updating using tray also contain checking for updates Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement checking of updates from multiple update channels Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Start installing updates automatically when quitting application Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Show application window when checking of updates has happened Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Show notifications and dialog for downloading update Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement naive notifications for version updates Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement checking of Electron specific updates as responsibility Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement downloading of Electron specific updates as responsibility Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for channel abstraction Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant global override Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix typing after enabling strict mode Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce abstraction for a state that is shared between environments Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract states of application update to be usable from all environments Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Handle failing download of update Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make code for window visibility actually work Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate code for sending messages between processes to a window Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Split bloated dependency in smaller pieces Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make state of download progress accessible from all environments Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Rename files for accuracy Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move channel abstraction to more global directory Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Enhance typing of channels and sync-box Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate channel abstraction types Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update asyncFn to support strict mode Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix snapshot after rebase Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing global override Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce injection token for channels to allow injecting all of them at once Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add notifications about change in update status Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Rename property for accuracy Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak code style Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make notifications unit testable in behaviours Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add implementation for asking boolean over processes Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Reorganize responsibilities for checking updates Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Reorganize tests for installing update under separate scenarios Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make stuff happening when root frame is rendered unit testable Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce periodical check for updates Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Allow downgrading app versions Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for checking of updates in application menu Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make test less prone to fail for wrong reason Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant boilerplate Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make tests for specific migrations less prone to failing for wrong reason Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move shared stuff under common Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using single source of truth for selected update channel Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract tests for installing update from different update channels to separate scenario Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing global override Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using release channel of installed application version as default value for selected update channel Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate usage of channel abstraction to same implementation Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make Channel abstraction support return values Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix direct calling of runnables Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Synchronize initial values of sync boxes when window starts Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing global override Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak message of question from user Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate names of directories Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add TODO Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove unimplemented scenario from test Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Simplify test Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Improve name of test Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant overrides Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix code style Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make Animate deterministic in unit tests Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Simplify naming Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify more naming Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify even more naming Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify more and more naming Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Add todo for cleaning unacceptable code encountered Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Improve name of behaviour Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make unit test more strict Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Enhance name of behaviour Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce dependency to get random IDs Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make asking of boolean value from user not require explicit ID for question Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify code for asking of boolean value from user Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make setting of initial state for sync boxes not trigger irrelevant messaging to main Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make a channel have default type for sent and returned message Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce higher order function to log errors in decorated functions Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Export type for error logging Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak test name Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce higher order function for suppressing errors Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Relocate some explicit error handlings to proper level of abstraction Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make higher order function for logging errors support asynchronous rejecting with non error instance Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make overridden version of application exactly the one required by unit test Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Mark injectable causing side effects Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Revert not required changes Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make code for asserting a promise more strict Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make dependencies readonly Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove duplication for disposers Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement initial values for sync-boxes Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Separate concept of message and request channels Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce tests for requesting from channel in renderer Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement requesting from renderer in main Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Revert "Implement requesting from renderer in main" This reverts commit d3e7899d7900516f3dbfacdb317a453202318305. Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak typing of request channel listeners to get rid of unexpected undefined Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove unused variable Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak timing of sentry setup Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Require messages for MessageChannels be JsonValues for serialization Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Require requests and responses for RequestChannels be JsonValues for serialization Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make different MessageChannels not require explicit "extends JsonObject" Note: Non-escaped lint breaks type here for forcing interface over type. Reasonable effort brought no understanding for what is the relevant difference between the two. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make a primitive argument an object for readability Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make typing of higher order function for error suppression not lie Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Serialize messages in channels to make IPC not blow up Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce a way to make intentional orphan promises uniform, controllable and deliberate Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make downloading of update and what follows more deliberate as orphan promise Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Move utility function under directory Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move another utility function under directory Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix incorrect name of file Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Round percentage of update download progress in tray Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix rebase conflicts Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix CheckForUpdate type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> Co-authored-by: Iku-turso <mikko.aspiala@gmail.com> Co-authored-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
229 files changed
+8652
-1217
No files matched your search
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* 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 trayInjectable from "./tray.injectable";
|
||||
import { onLoadOfApplicationInjectionToken } from "../start-main-application/runnable-tokens/on-load-of-application-injection-token";
|
||||
|
||||
const installTrayInjectable = getInjectable({
|
||||
id: "install-tray",
|
||||
|
||||
instantiate: (di) => {
|
||||
const trayInitializer = di.inject(trayInjectable);
|
||||
|
||||
return {
|
||||
run: async () => {
|
||||
await trayInitializer.start();
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: onLoadOfApplicationInjectionToken,
|
||||
});
|
||||
|
||||
export default installTrayInjectable;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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 productNameInjectable from "../../../app-paths/app-name/product-name.injectable";
|
||||
import showApplicationWindowInjectable from "../../../start-main-application/lens-window/show-application-window.injectable";
|
||||
import showAboutInjectable from "../../../menu/show-about.injectable";
|
||||
import { trayMenuItemInjectionToken } from "../tray-menu-item-injection-token";
|
||||
import { computed } from "mobx";
|
||||
import withErrorLoggingInjectable from "../../../../common/utils/with-error-logging/with-error-logging.injectable";
|
||||
import { withErrorSuppression } from "../../../../common/utils/with-error-suppression/with-error-suppression";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
|
||||
const aboutAppTrayItemInjectable = getInjectable({
|
||||
id: "about-app-tray-item",
|
||||
|
||||
instantiate: (di) => {
|
||||
const productName = di.inject(productNameInjectable);
|
||||
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
|
||||
const showAbout = di.inject(showAboutInjectable);
|
||||
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
||||
|
||||
return {
|
||||
id: "about-app",
|
||||
parentId: null,
|
||||
orderNumber: 140,
|
||||
label: computed(() => `About ${productName}`),
|
||||
enabled: computed(() => true),
|
||||
visible: computed(() => true),
|
||||
|
||||
click: pipeline(
|
||||
async () => {
|
||||
await showApplicationWindow();
|
||||
|
||||
await showAbout();
|
||||
},
|
||||
|
||||
withErrorLoggingFor(() => "[TRAY]: Opening of show about failed."),
|
||||
|
||||
// TODO: Find out how to improve typing so that instead of
|
||||
// x => withErrorSuppression(x) there could only be withErrorSuppression
|
||||
(x) => withErrorSuppression(x),
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: trayMenuItemInjectionToken,
|
||||
});
|
||||
|
||||
export default aboutAppTrayItemInjectable;
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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 { trayMenuItemInjectionToken } from "../tray-menu-item-injection-token";
|
||||
import productNameInjectable from "../../../app-paths/app-name/product-name.injectable";
|
||||
import showApplicationWindowInjectable from "../../../start-main-application/lens-window/show-application-window.injectable";
|
||||
import { computed } from "mobx";
|
||||
import withErrorLoggingInjectable from "../../../../common/utils/with-error-logging/with-error-logging.injectable";
|
||||
import { withErrorSuppression } from "../../../../common/utils/with-error-suppression/with-error-suppression";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
|
||||
const openAppTrayItemInjectable = getInjectable({
|
||||
id: "open-app-tray-item",
|
||||
|
||||
instantiate: (di) => {
|
||||
const productName = di.inject(productNameInjectable);
|
||||
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
|
||||
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
||||
|
||||
return {
|
||||
id: "open-app",
|
||||
parentId: null,
|
||||
label: computed(() => `Open ${productName}`),
|
||||
orderNumber: 10,
|
||||
enabled: computed(() => true),
|
||||
visible: computed(() => true),
|
||||
|
||||
click: pipeline(
|
||||
async () => {
|
||||
await showApplicationWindow();
|
||||
},
|
||||
|
||||
withErrorLoggingFor(() => "[TRAY]: Opening of application window failed."),
|
||||
|
||||
// TODO: Find out how to improve typing so that instead of
|
||||
// x => withErrorSuppression(x) there could only be withErrorSuppression
|
||||
(x) => withErrorSuppression(x),
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: trayMenuItemInjectionToken,
|
||||
});
|
||||
|
||||
export default openAppTrayItemInjectable;
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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 { trayMenuItemInjectionToken } from "../tray-menu-item-injection-token";
|
||||
import navigateToPreferencesInjectable from "../../../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable";
|
||||
import { computed } from "mobx";
|
||||
import { withErrorSuppression } from "../../../../common/utils/with-error-suppression/with-error-suppression";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import withErrorLoggingInjectable from "../../../../common/utils/with-error-logging/with-error-logging.injectable";
|
||||
|
||||
const openPreferencesTrayItemInjectable = getInjectable({
|
||||
id: "open-preferences-tray-item",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToPreferences = di.inject(navigateToPreferencesInjectable);
|
||||
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
||||
|
||||
return {
|
||||
id: "open-preferences",
|
||||
parentId: null,
|
||||
label: computed(() => "Preferences"),
|
||||
orderNumber: 20,
|
||||
enabled: computed(() => true),
|
||||
visible: computed(() => true),
|
||||
|
||||
click: pipeline(
|
||||
navigateToPreferences,
|
||||
|
||||
withErrorLoggingFor(() => "[TRAY]: Opening of preferences failed."),
|
||||
|
||||
// TODO: Find out how to improve typing so that instead of
|
||||
// x => withErrorSuppression(x) there could only be withErrorSuppression
|
||||
(x) => withErrorSuppression(x),
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: trayMenuItemInjectionToken,
|
||||
});
|
||||
|
||||
export default openPreferencesTrayItemInjectable;
|
||||
+24
@@ -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 { trayMenuItemInjectionToken } from "../tray-menu-item-injection-token";
|
||||
import { computed } from "mobx";
|
||||
|
||||
const quitAppSeparatorTrayItemInjectable = getInjectable({
|
||||
id: "quit-app-separator-tray-item",
|
||||
|
||||
instantiate: () => ({
|
||||
id: "quit-app-separator",
|
||||
parentId: null,
|
||||
orderNumber: 149,
|
||||
enabled: computed(() => true),
|
||||
visible: computed(() => true),
|
||||
separator: true,
|
||||
}),
|
||||
|
||||
injectionToken: trayMenuItemInjectionToken,
|
||||
});
|
||||
|
||||
export default quitAppSeparatorTrayItemInjectable;
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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 { trayMenuItemInjectionToken } from "../tray-menu-item-injection-token";
|
||||
import { computed } from "mobx";
|
||||
import stopServicesAndExitAppInjectable from "../../../stop-services-and-exit-app.injectable";
|
||||
import { withErrorSuppression } from "../../../../common/utils/with-error-suppression/with-error-suppression";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import withErrorLoggingInjectable from "../../../../common/utils/with-error-logging/with-error-logging.injectable";
|
||||
|
||||
const quitAppTrayItemInjectable = getInjectable({
|
||||
id: "quit-app-tray-item",
|
||||
|
||||
instantiate: (di) => {
|
||||
const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable);
|
||||
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
||||
|
||||
return {
|
||||
id: "quit-app",
|
||||
parentId: null,
|
||||
orderNumber: 150,
|
||||
label: computed(() => "Quit App"),
|
||||
enabled: computed(() => true),
|
||||
visible: computed(() => true),
|
||||
|
||||
click: pipeline(
|
||||
stopServicesAndExitApp,
|
||||
|
||||
withErrorLoggingFor(() => "[TRAY]: Quitting application failed."),
|
||||
|
||||
// TODO: Find out how to improve typing so that instead of
|
||||
// x => withErrorSuppression(x) there could only be withErrorSuppression
|
||||
(x) => withErrorSuppression(x),
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: trayMenuItemInjectionToken,
|
||||
});
|
||||
|
||||
export default quitAppTrayItemInjectable;
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
|
||||
|
||||
export interface TrayMenuItem {
|
||||
id: string;
|
||||
parentId: string | null;
|
||||
orderNumber: number;
|
||||
enabled: IComputedValue<boolean>;
|
||||
visible: IComputedValue<boolean>;
|
||||
|
||||
label?: IComputedValue<string>;
|
||||
click?: () => Promise<void> | void;
|
||||
tooltip?: string;
|
||||
separator?: boolean;
|
||||
extension?: LensMainExtension;
|
||||
}
|
||||
|
||||
export const trayMenuItemInjectionToken = getInjectionToken<TrayMenuItem>({
|
||||
id: "tray-menu-item",
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { flatMap, kebabCase } from "lodash/fp";
|
||||
import type { Injectable } from "@ogre-tools/injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token";
|
||||
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
|
||||
import type { TrayMenuItem } from "./tray-menu-item-injection-token";
|
||||
import { trayMenuItemInjectionToken } from "./tray-menu-item-injection-token";
|
||||
import type { TrayMenuRegistration } from "../tray-menu-registration";
|
||||
import { withErrorSuppression } from "../../../common/utils/with-error-suppression/with-error-suppression";
|
||||
import type { WithErrorLoggingFor } from "../../../common/utils/with-error-logging/with-error-logging.injectable";
|
||||
import withErrorLoggingInjectable from "../../../common/utils/with-error-logging/with-error-logging.injectable";
|
||||
|
||||
const trayMenuItemRegistratorInjectable = getInjectable({
|
||||
id: "tray-menu-item-registrator",
|
||||
|
||||
instantiate: (di) => (extension, installationCounter) => {
|
||||
const mainExtension = extension as LensMainExtension;
|
||||
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
||||
|
||||
pipeline(
|
||||
mainExtension.trayMenus,
|
||||
|
||||
flatMap(toItemInjectablesFor(mainExtension, installationCounter, withErrorLoggingFor)),
|
||||
|
||||
(injectables) => di.register(...injectables),
|
||||
);
|
||||
},
|
||||
|
||||
injectionToken: extensionRegistratorInjectionToken,
|
||||
});
|
||||
|
||||
export default trayMenuItemRegistratorInjectable;
|
||||
|
||||
const toItemInjectablesFor = (extension: LensMainExtension, installationCounter: number, withErrorLoggingFor: WithErrorLoggingFor) => {
|
||||
const _toItemInjectables = (parentId: string | null) => (registration: TrayMenuRegistration): Injectable<TrayMenuItem, TrayMenuItem, void>[] => {
|
||||
const trayItemId = registration.id || kebabCase(registration.label || "");
|
||||
const id = `${trayItemId}-tray-menu-item-for-extension-${extension.sanitizedExtensionId}-instance-${installationCounter}`;
|
||||
|
||||
const parentInjectable = getInjectable({
|
||||
id,
|
||||
|
||||
instantiate: () => ({
|
||||
id,
|
||||
parentId,
|
||||
orderNumber: 100,
|
||||
|
||||
separator: registration.type === "separator",
|
||||
|
||||
label: computed(() => registration.label || ""),
|
||||
tooltip: registration.toolTip,
|
||||
|
||||
click: pipeline(
|
||||
() => {
|
||||
registration.click?.(registration);
|
||||
},
|
||||
|
||||
withErrorLoggingFor(() => `[TRAY]: Clicking of tray item "${trayItemId}" from extension "${extension.sanitizedExtensionId}" failed.`),
|
||||
|
||||
// TODO: Find out how to improve typing so that instead of
|
||||
// x => withErrorSuppression(x) there could only be withErrorSuppression
|
||||
(x) => withErrorSuppression(x),
|
||||
),
|
||||
|
||||
enabled: computed(() => !!registration.enabled),
|
||||
visible: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: trayMenuItemInjectionToken,
|
||||
});
|
||||
|
||||
const childMenuItems = registration.submenu || [];
|
||||
|
||||
const childInjectables = childMenuItems.flatMap(_toItemInjectables(id));
|
||||
|
||||
return [
|
||||
parentInjectable,
|
||||
...childInjectables,
|
||||
];
|
||||
};
|
||||
|
||||
return _toItemInjectables(null);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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 { computed } from "mobx";
|
||||
|
||||
|
||||
import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable";
|
||||
import type { TrayMenuItem } from "./tray-menu-item-injection-token";
|
||||
import { trayMenuItemInjectionToken } from "./tray-menu-item-injection-token";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { filter, overSome, sortBy } from "lodash/fp";
|
||||
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
|
||||
|
||||
const trayMenuItemsInjectable = getInjectable({
|
||||
id: "tray-menu-items",
|
||||
|
||||
instantiate: (di) => {
|
||||
const extensions = di.inject(mainExtensionsInjectable);
|
||||
|
||||
return computed(() => {
|
||||
const enabledExtensions = extensions.get();
|
||||
|
||||
return pipeline(
|
||||
di.injectMany(trayMenuItemInjectionToken),
|
||||
|
||||
filter((item) =>
|
||||
overSome([
|
||||
isNonExtensionItem,
|
||||
isEnabledExtensionItemFor(enabledExtensions),
|
||||
])(item),
|
||||
),
|
||||
|
||||
filter(item => item.visible.get()),
|
||||
items => sortBy("orderNumber", items),
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const isNonExtensionItem = (item: TrayMenuItem) => !item.extension;
|
||||
|
||||
const isEnabledExtensionItemFor =
|
||||
(enabledExtensions: LensMainExtension[]) => (item: TrayMenuItem) =>
|
||||
!!enabledExtensions.find((extension) => extension === item.extension);
|
||||
|
||||
|
||||
export default trayMenuItemsInjectable;
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* 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 { initTray } from "./tray";
|
||||
import trayMenuItemsInjectable from "./tray-menu-items.injectable";
|
||||
import navigateToPreferencesInjectable from "../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable";
|
||||
import stopServicesAndExitAppInjectable from "../stop-services-and-exit-app.injectable";
|
||||
import { getStartableStoppable } from "../../common/utils/get-startable-stoppable";
|
||||
import isAutoUpdateEnabledInjectable from "../is-auto-update-enabled.injectable";
|
||||
import showAboutInjectable from "../menu/show-about.injectable";
|
||||
import showApplicationWindowInjectable from "../start-main-application/lens-window/show-application-window.injectable";
|
||||
import trayIconPathInjectable from "./tray-icon-path.injectable";
|
||||
|
||||
const trayInjectable = getInjectable({
|
||||
id: "tray",
|
||||
|
||||
instantiate: (di) => {
|
||||
const trayMenuItems = di.inject(trayMenuItemsInjectable);
|
||||
const navigateToPreferences = di.inject(navigateToPreferencesInjectable);
|
||||
const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable);
|
||||
const isAutoUpdateEnabled = di.inject(isAutoUpdateEnabledInjectable);
|
||||
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
|
||||
const showAboutPopup = di.inject(showAboutInjectable);
|
||||
const trayIconPath = di.inject(trayIconPathInjectable);
|
||||
|
||||
return getStartableStoppable("build-of-tray", () =>
|
||||
initTray(
|
||||
trayMenuItems,
|
||||
navigateToPreferences,
|
||||
stopServicesAndExitApp,
|
||||
isAutoUpdateEnabled,
|
||||
showApplicationWindow,
|
||||
showAboutPopup,
|
||||
trayIconPath,
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default trayInjectable;
|
||||
+48
-70
@@ -7,25 +7,22 @@ import packageInfo from "../../../package.json";
|
||||
import { Menu, Tray } from "electron";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { autorun } from "mobx";
|
||||
import { checkForUpdates } from "../app-updater";
|
||||
import logger from "../logger";
|
||||
import { isWindows, productName } from "../../common/vars";
|
||||
import { isWindows } from "../../common/vars";
|
||||
import type { Disposer } from "../../common/utils";
|
||||
import { disposer, toJS } from "../../common/utils";
|
||||
import type { TrayMenuRegistration } from "./tray-menu-registration";
|
||||
import { disposer } from "../../common/utils";
|
||||
import type { TrayMenuItem } from "./tray-menu-item/tray-menu-item-injection-token";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { filter, isEmpty, map } from "lodash/fp";
|
||||
|
||||
const TRAY_LOG_PREFIX = "[TRAY]";
|
||||
export const TRAY_LOG_PREFIX = "[TRAY]";
|
||||
|
||||
// note: instance of Tray should be saved somewhere, otherwise it disappears
|
||||
export let tray: Tray | null = null;
|
||||
|
||||
export function initTray(
|
||||
trayMenuItems: IComputedValue<TrayMenuRegistration[]>,
|
||||
navigateToPreferences: () => void,
|
||||
stopServicesAndExitApp: () => void,
|
||||
isAutoUpdateEnabled: () => boolean,
|
||||
trayMenuItems: IComputedValue<TrayMenuItem[]>,
|
||||
showApplicationWindow: () => Promise<void>,
|
||||
showAbout: () => void,
|
||||
trayIconPath: string,
|
||||
): Disposer {
|
||||
tray = new Tray(trayIconPath);
|
||||
@@ -42,7 +39,9 @@ export function initTray(
|
||||
return disposer(
|
||||
autorun(() => {
|
||||
try {
|
||||
const menu = createTrayMenu(toJS(trayMenuItems.get()), navigateToPreferences, stopServicesAndExitApp, isAutoUpdateEnabled, showApplicationWindow, showAbout);
|
||||
const options = toTrayMenuOptions(trayMenuItems.get());
|
||||
|
||||
const menu = Menu.buildFromTemplate(options);
|
||||
|
||||
tray?.setContextMenu(menu);
|
||||
} catch (error) {
|
||||
@@ -56,66 +55,45 @@ export function initTray(
|
||||
);
|
||||
}
|
||||
|
||||
function getMenuItemConstructorOptions(trayItem: TrayMenuRegistration): Electron.MenuItemConstructorOptions {
|
||||
return {
|
||||
...trayItem,
|
||||
submenu: trayItem.submenu ? trayItem.submenu.map(getMenuItemConstructorOptions) : undefined,
|
||||
click: trayItem.click ? () => {
|
||||
trayItem.click?.(trayItem);
|
||||
} : undefined,
|
||||
};
|
||||
}
|
||||
const toTrayMenuOptions = (trayMenuItems: TrayMenuItem[]) => {
|
||||
const _toTrayMenuOptions = (parentId: string | null) =>
|
||||
pipeline(
|
||||
trayMenuItems,
|
||||
|
||||
function createTrayMenu(
|
||||
extensionTrayItems: TrayMenuRegistration[],
|
||||
navigateToPreferences: () => void,
|
||||
stopServicesAndExitApp: () => void,
|
||||
isAutoUpdateEnabled: () => boolean,
|
||||
showApplicationWindow: () => Promise<void>,
|
||||
showAbout: () => void,
|
||||
): Menu {
|
||||
let template: Electron.MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: `Open ${productName}`,
|
||||
click() {
|
||||
showApplicationWindow().catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to open lens`, { error }));
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Preferences",
|
||||
click() {
|
||||
navigateToPreferences();
|
||||
},
|
||||
},
|
||||
];
|
||||
filter((item) => item.parentId === parentId),
|
||||
|
||||
if (isAutoUpdateEnabled()) {
|
||||
template.push({
|
||||
label: "Check for updates",
|
||||
click() {
|
||||
checkForUpdates()
|
||||
.then(() => showApplicationWindow());
|
||||
},
|
||||
});
|
||||
}
|
||||
map(
|
||||
(trayMenuItem: TrayMenuItem): Electron.MenuItemConstructorOptions => {
|
||||
if (trayMenuItem.separator) {
|
||||
return { id: trayMenuItem.id, type: "separator" };
|
||||
}
|
||||
|
||||
template = template.concat(extensionTrayItems.map(getMenuItemConstructorOptions));
|
||||
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: () => {
|
||||
trayMenuItem.click?.();
|
||||
},
|
||||
}
|
||||
: {
|
||||
type: "submenu",
|
||||
submenu: _toTrayMenuOptions(trayMenuItem.id),
|
||||
}),
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return _toTrayMenuOptions(null);
|
||||
};
|
||||
|
||||
return Menu.buildFromTemplate(template.concat([
|
||||
{
|
||||
label: `About ${productName}`,
|
||||
click() {
|
||||
showApplicationWindow()
|
||||
.then(showAbout)
|
||||
.catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to show Lens About view`, { error }));
|
||||
},
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Quit App",
|
||||
click() {
|
||||
stopServicesAndExitApp();
|
||||
},
|
||||
},
|
||||
]));
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* 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 trayInjectable from "./tray.injectable";
|
||||
import { beforeQuitOfBackEndInjectionToken } from "../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
|
||||
|
||||
const uninstallTrayInjectable = getInjectable({
|
||||
id: "uninstall-tray",
|
||||
|
||||
instantiate: (di) => {
|
||||
const trayInitializer = di.inject(trayInjectable);
|
||||
|
||||
return {
|
||||
run: async () => {
|
||||
await trayInitializer.stop();
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: beforeQuitOfBackEndInjectionToken,
|
||||
});
|
||||
|
||||
export default uninstallTrayInjectable;
|
||||
Reference in new issue
Block a user