From 23cbc2a2af99c9ca758379b0acab4edd3f82032f Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Fri, 1 Jul 2022 14:57:14 +0300 Subject: [PATCH] Make adding of new tray icons easier by complying to Open Closed Principle Signed-off-by: Janne Savolainen --- ...application-update-tray-item.injectable.ts | 8 ++-- ...dy-to-be-installed-tray-icon.injectable.ts | 28 ++++++++++++++ ...ate-is-ready-to-be-installed.injectable.ts | 25 +++++++++++++ .../electron-tray/electron-tray.injectable.ts | 6 +-- .../get-tray-icon-path.injectable.ts | 37 +++++++++++++++++++ .../menu-icon/normal-tray-icon.injectable.ts | 26 +++++++++++++ .../tray/menu-icon/reactive.injectable.ts | 17 +++------ .../menu-icon/tray-icon-injection-token.ts | 16 ++++++++ .../tray/menu-icon/tray-icon.injectable.ts | 33 +++++++++++++++++ src/main/tray/tray-icon-path.injectable.ts | 37 ------------------- .../test-utils/get-application-builder.tsx | 7 +--- 11 files changed, 178 insertions(+), 62 deletions(-) create mode 100644 src/main/application-update/tray-icons/update-is-ready-to-be-installed-tray-icon.injectable.ts create mode 100644 src/main/application-update/update-is-ready-to-be-installed.injectable.ts create mode 100644 src/main/tray/menu-icon/get-tray-icon-path.injectable.ts create mode 100644 src/main/tray/menu-icon/normal-tray-icon.injectable.ts create mode 100644 src/main/tray/menu-icon/tray-icon-injection-token.ts create mode 100644 src/main/tray/menu-icon/tray-icon.injectable.ts delete mode 100644 src/main/tray/tray-icon-path.injectable.ts diff --git a/src/main/application-update/install-application-update-tray-item.injectable.ts b/src/main/application-update/install-application-update-tray-item.injectable.ts index a7a63d1a1b..ba71d5cfe7 100644 --- a/src/main/application-update/install-application-update-tray-item.injectable.ts +++ b/src/main/application-update/install-application-update-tray-item.injectable.ts @@ -6,11 +6,11 @@ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; import { trayMenuItemInjectionToken } from "../tray/tray-menu-item/tray-menu-item-injection-token"; import discoveredUpdateVersionInjectable from "../../common/application-update/discovered-update-version/discovered-update-version.injectable"; -import updateIsBeingDownloadedInjectable from "../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.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"; import quitAndInstallUpdateInjectable from "./quit-and-install-update.injectable"; +import updateIsReadyToBeInstalledInjectable from "./update-is-ready-to-be-installed.injectable"; const installApplicationUpdateTrayItemInjectable = getInjectable({ id: "install-update-tray-item", @@ -18,8 +18,8 @@ const installApplicationUpdateTrayItemInjectable = getInjectable({ instantiate: (di) => { const quitAndInstallUpdate = di.inject(quitAndInstallUpdateInjectable); const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable); - const downloadingUpdateState = di.inject(updateIsBeingDownloadedInjectable); const withErrorLoggingFor = di.inject(withErrorLoggingInjectable); + const updateIsReadyToBeInstalled = di.inject(updateIsReadyToBeInstalledInjectable); return { id: "install-update", @@ -34,9 +34,7 @@ const installApplicationUpdateTrayItemInjectable = getInjectable({ enabled: computed(() => true), - visible: computed( - () => !!discoveredVersionState.value.get() && !downloadingUpdateState.value.get(), - ), + visible: updateIsReadyToBeInstalled, click: pipeline( quitAndInstallUpdate, diff --git a/src/main/application-update/tray-icons/update-is-ready-to-be-installed-tray-icon.injectable.ts b/src/main/application-update/tray-icons/update-is-ready-to-be-installed-tray-icon.injectable.ts new file mode 100644 index 0000000000..75fd9f3820 --- /dev/null +++ b/src/main/application-update/tray-icons/update-is-ready-to-be-installed-tray-icon.injectable.ts @@ -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 { computed } from "mobx"; +import getTrayIconPathInjectable from "../../tray/menu-icon/get-tray-icon-path.injectable"; +import { trayIconInjectionToken } from "../../tray/menu-icon/tray-icon-injection-token"; +import updateIsReadyToBeInstalledInjectable from "../update-is-ready-to-be-installed.injectable"; + +const updateIsReadyToBeInstalledTrayIconInjectable = getInjectable({ + id: "update-is-ready-to-be-installed-tray-icon", + + instantiate: (di) => { + const getTrayIconPath = di.inject(getTrayIconPathInjectable); + const updateIsReadyToBeInstalled = di.inject(updateIsReadyToBeInstalledInjectable); + + return { + iconPath: getTrayIconPath("update-available"), + priority: 1, + shouldBeShown: computed(() => updateIsReadyToBeInstalled.get()), + }; + }, + + injectionToken: trayIconInjectionToken, +}); + +export default updateIsReadyToBeInstalledTrayIconInjectable; diff --git a/src/main/application-update/update-is-ready-to-be-installed.injectable.ts b/src/main/application-update/update-is-ready-to-be-installed.injectable.ts new file mode 100644 index 0000000000..157d1bbd6c --- /dev/null +++ b/src/main/application-update/update-is-ready-to-be-installed.injectable.ts @@ -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 { computed } from "mobx"; +import discoveredUpdateVersionInjectable from "../../common/application-update/discovered-update-version/discovered-update-version.injectable"; +import updateIsBeingDownloadedInjectable from "../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable"; + +const updateIsReadyToBeInstalledInjectable = getInjectable({ + id: "update-is-ready-to-be-installed", + + instantiate: (di) => { + const discoveredUpdateVersion = di.inject(discoveredUpdateVersionInjectable); + const updateIsBeingDownloaded = di.inject(updateIsBeingDownloadedInjectable); + + return computed( + () => + !!discoveredUpdateVersion.value.get() && + !updateIsBeingDownloaded.value.get(), + ); + }, +}); + +export default updateIsReadyToBeInstalledInjectable; diff --git a/src/main/tray/electron-tray/electron-tray.injectable.ts b/src/main/tray/electron-tray/electron-tray.injectable.ts index d7547ca57a..d8b264060a 100644 --- a/src/main/tray/electron-tray/electron-tray.injectable.ts +++ b/src/main/tray/electron-tray/electron-tray.injectable.ts @@ -8,8 +8,8 @@ import packageJsonInjectable from "../../../common/vars/package-json.injectable" import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable"; import isWindowsInjectable from "../../../common/vars/is-windows.injectable"; import loggerInjectable from "../../../common/logger.injectable"; -import trayIconPathsInjectable from "../tray-icon-path.injectable"; import { convertToElectronMenuTemplate } from "../reactive-tray-menu-items/converters"; +import trayIconInjectable from "../menu-icon/tray-icon.injectable"; const TRAY_LOG_PREFIX = "[TRAY]"; @@ -38,13 +38,13 @@ const electronTrayInjectable = getInjectable({ const showApplicationWindow = di.inject(showApplicationWindowInjectable); const isWindows = di.inject(isWindowsInjectable); const logger = di.inject(loggerInjectable); - const trayIconPaths = di.inject(trayIconPathsInjectable); + const trayIcon = di.inject(trayIconInjectable); let tray: Tray; return { start: () => { - tray = new Tray(trayIconPaths.normal); + tray = new Tray(trayIcon.get().iconPath); tray.setToolTip(packageJson.description); tray.setIgnoreDoubleClickEvents(true); diff --git a/src/main/tray/menu-icon/get-tray-icon-path.injectable.ts b/src/main/tray/menu-icon/get-tray-icon-path.injectable.ts new file mode 100644 index 0000000000..fe2746a2ab --- /dev/null +++ b/src/main/tray/menu-icon/get-tray-icon-path.injectable.ts @@ -0,0 +1,37 @@ +/** + * 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 getAbsolutePathInjectable from "../../../common/path/get-absolute-path.injectable"; +import staticFilesDirectoryInjectable from "../../../common/vars/static-files-directory.injectable"; +import isDevelopmentInjectable from "../../../common/vars/is-development.injectable"; +import isMacInjectable from "../../../common/vars/is-mac.injectable"; +import { camelCase, flow, upperFirst } from "lodash/fp"; +const upperCamelCase = flow(camelCase, upperFirst); + +const getTrayIconPathInjectable = getInjectable({ + id: "get-tray-icon-path", + + instantiate: (di) => { + const getAbsolutePath = di.inject(getAbsolutePathInjectable); + const staticFilesDirectory = di.inject(staticFilesDirectoryInjectable); + const isDevelopment = di.inject(isDevelopmentInjectable); + const isMac = di.inject(isMacInjectable); + + const baseIconDirectory = getAbsolutePath( + staticFilesDirectory, + isDevelopment ? "../build/tray" : "icons", // copied within electron-builder extras + ); + + const fileSuffix = isMac ? "Template.png" : ".png"; + + return (name: string) => + getAbsolutePath( + baseIconDirectory, + `trayIcon${upperCamelCase(name)}${fileSuffix}`, + ); + }, +}); + +export default getTrayIconPathInjectable; diff --git a/src/main/tray/menu-icon/normal-tray-icon.injectable.ts b/src/main/tray/menu-icon/normal-tray-icon.injectable.ts new file mode 100644 index 0000000000..53c9679b65 --- /dev/null +++ b/src/main/tray/menu-icon/normal-tray-icon.injectable.ts @@ -0,0 +1,26 @@ +/** + * 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 { trayIconInjectionToken } from "./tray-icon-injection-token"; +import getTrayIconPathInjectable from "./get-tray-icon-path.injectable"; + +const normalTrayIconInjectable = getInjectable({ + id: "normal-icon", + + instantiate: (di) => { + const getTrayIconPath = di.inject(getTrayIconPathInjectable); + + return { + iconPath: getTrayIconPath(""), + priority: 999, + shouldBeShown: computed(() => true), + }; + }, + + injectionToken: trayIconInjectionToken, +}); + +export default normalTrayIconInjectable; diff --git a/src/main/tray/menu-icon/reactive.injectable.ts b/src/main/tray/menu-icon/reactive.injectable.ts index 42622ff2a8..8c6d358477 100644 --- a/src/main/tray/menu-icon/reactive.injectable.ts +++ b/src/main/tray/menu-icon/reactive.injectable.ts @@ -4,27 +4,22 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { reaction } from "mobx"; -import discoveredUpdateVersionInjectable from "../../../common/application-update/discovered-update-version/discovered-update-version.injectable"; import { getStartableStoppable } from "../../../common/utils/get-startable-stoppable"; import electronTrayInjectable from "../electron-tray/electron-tray.injectable"; -import trayIconPathsInjectable from "../tray-icon-path.injectable"; +import trayIconInjectable from "./tray-icon.injectable"; const reactiveTrayMenuIconInjectable = getInjectable({ id: "reactive-tray-menu-icon", + instantiate: (di) => { - const discoveredUpdateVersion = di.inject(discoveredUpdateVersionInjectable); + const trayMenuIcon = di.inject(trayIconInjectable); const electronTray = di.inject(electronTrayInjectable); - const trayIconPaths = di.inject(trayIconPathsInjectable); return getStartableStoppable("reactive-tray-menu-icon", () => ( reaction( - () => discoveredUpdateVersion.value.get(), - updateVersion => { - if (updateVersion) { - electronTray.setIconPath(trayIconPaths.updateAvailable); - } else { - electronTray.setIconPath(trayIconPaths.normal); - } + () => trayMenuIcon.get(), + icon => { + electronTray.setIconPath(icon.iconPath); }, { fireImmediately: true, diff --git a/src/main/tray/menu-icon/tray-icon-injection-token.ts b/src/main/tray/menu-icon/tray-icon-injection-token.ts new file mode 100644 index 0000000000..a1e678d107 --- /dev/null +++ b/src/main/tray/menu-icon/tray-icon-injection-token.ts @@ -0,0 +1,16 @@ +/** + * 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"; + +export interface TrayIcon { + iconPath: string; + priority: number; + shouldBeShown: IComputedValue; +} + +export const trayIconInjectionToken = getInjectionToken({ + id: "tray-icon-token", +}); diff --git a/src/main/tray/menu-icon/tray-icon.injectable.ts b/src/main/tray/menu-icon/tray-icon.injectable.ts new file mode 100644 index 0000000000..dd2cc7bcb3 --- /dev/null +++ b/src/main/tray/menu-icon/tray-icon.injectable.ts @@ -0,0 +1,33 @@ +/** + * 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 { find, sortBy } from "lodash/fp"; +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { trayIconInjectionToken } from "./tray-icon-injection-token"; + +const trayIconInjectable = getInjectable({ + id: "tray-icon", + + instantiate: (di) => { + const availableIcons = di.injectMany(trayIconInjectionToken); + + return computed(() => { + const mostPrioritizedIcon = pipeline( + availableIcons, + sortBy((icon) => icon.priority), + find((icon) => icon.shouldBeShown.get()), + ); + + if (!mostPrioritizedIcon) { + throw new Error("There should always be tray icon which is shown."); + } + + return mostPrioritizedIcon; + }); + }, +}); + +export default trayIconInjectable; diff --git a/src/main/tray/tray-icon-path.injectable.ts b/src/main/tray/tray-icon-path.injectable.ts deleted file mode 100644 index df83a2e31c..0000000000 --- a/src/main/tray/tray-icon-path.injectable.ts +++ /dev/null @@ -1,37 +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 getAbsolutePathInjectable from "../../common/path/get-absolute-path.injectable"; -import staticFilesDirectoryInjectable from "../../common/vars/static-files-directory.injectable"; -import isDevelopmentInjectable from "../../common/vars/is-development.injectable"; -import isMacInjectable from "../../common/vars/is-mac.injectable"; - -export interface TrayIconPaths { - normal: string; - updateAvailable: string; -} - -const trayIconPathsInjectable = getInjectable({ - id: "tray-icon-paths", - - instantiate: (di): TrayIconPaths => { - const getAbsolutePath = di.inject(getAbsolutePathInjectable); - const staticFilesDirectory = di.inject(staticFilesDirectoryInjectable); - const isDevelopment = di.inject(isDevelopmentInjectable); - const isMac = di.inject(isMacInjectable); - const baseIconDirectory = getAbsolutePath( - staticFilesDirectory, - isDevelopment ? "../build/tray" : "icons", // copied within electron-builder extras - ); - const fileSuffix = isMac ? "Template.png" : ".png"; - - return { - normal: getAbsolutePath(baseIconDirectory, `trayIcon${fileSuffix}`), - updateAvailable: getAbsolutePath(baseIconDirectory, `trayIconUpdateAvailable${fileSuffix}`), - }; - }, -}); - -export default trayIconPathsInjectable; diff --git a/src/renderer/components/test-utils/get-application-builder.tsx b/src/renderer/components/test-utils/get-application-builder.tsx index 80a6627f50..e6fb639adc 100644 --- a/src/renderer/components/test-utils/get-application-builder.tsx +++ b/src/renderer/components/test-utils/get-application-builder.tsx @@ -43,7 +43,6 @@ import applicationWindowInjectable from "../../../main/start-main-application/le import { getDiForUnitTesting as getRendererDi } from "../../getDiForUnitTesting"; import { getDiForUnitTesting as getMainDi } from "../../../main/getDiForUnitTesting"; import { overrideChannels } from "../../../test-utils/channel-fakes/override-channels"; -import trayIconPathsInjectable from "../../../main/tray/tray-icon-path.injectable"; import assert from "assert"; import { openMenu } from "react-select-event"; import userEvent from "@testing-library/user-event"; @@ -194,11 +193,7 @@ export const getApplicationBuilder = () => { const traySetMenuItemsMock = jest.fn(); mainDi.override(electronTrayInjectable, () => ({ - start: () => { - const iconPaths = mainDi.inject(trayIconPathsInjectable); - - trayMenuIconPath = iconPaths.normal; - }, + start: () => {}, stop: () => {}, setMenuItems: traySetMenuItemsMock, setIconPath: (path) => {