diff --git a/build/generate-tray-icons.ts b/build/generate-tray-icons.ts index ed90d27832..487ba8d348 100644 --- a/build/generate-tray-icons.ts +++ b/build/generate-tray-icons.ts @@ -12,6 +12,7 @@ const size = Number(process.env.OUTPUT_SIZE || "16"); const outputFolder = process.env.OUTPUT_DIR || "./build/tray"; const inputFile = process.env.INPUT_SVG_PATH || "./src/renderer/components/icon/logo-lens.svg"; const noticeFile = process.env.NOTICE_SVG_PATH || "./src/renderer/components/icon/notice.svg"; +const spinnerFile = process.env.SPINNER_SVG_PATH || "./src/renderer/components/icon/arrow-spinner.svg"; async function ensureOutputFoler() { await ensureDir(outputFolder); @@ -58,8 +59,9 @@ async function generateImages(image: Buffer, size: number, name: string) { ]); } -async function generateUpdateAvailableImages(baseImage: Buffer, system: TargetSystems) { - const noticeIconImage = await getNoticeIconImage(system); +async function generateImageWithSvg(baseImage: Buffer, system: TargetSystems, filePath: string) { + const svgFile = await getIconImage(system, filePath); + const circleBuffer = await sharp(Buffer.from(` @@ -78,7 +80,7 @@ async function generateUpdateAvailableImages(baseImage: Buffer, system: TargetSy }, { input: ( - await sharp(noticeIconImage) + await sharp(svgFile) .resize({ width: 60, height: 60, @@ -92,8 +94,8 @@ async function generateUpdateAvailableImages(baseImage: Buffer, system: TargetSy .toBuffer(); } -async function getNoticeIconImage(system: TargetSystems) { - const svgData = await readFile(noticeFile, { encoding: "utf-8" }); +async function getIconImage(system: TargetSystems, filePath: string) { + const svgData = await readFile(filePath, { encoding: "utf-8" }); const root = new JSDOM(svgData).window.document.getElementsByTagName("svg")[0]; root.innerHTML += getSvgStyling(system === "macos" ? "light" : "dark"); @@ -107,18 +109,25 @@ async function generateTrayIcons() { await ensureOutputFoler(); const baseIconTemplateImage = await getBaseIconImage("macos"); - const updateAvailableTemplateImage = await generateUpdateAvailableImages(baseIconTemplateImage, "macos"); const baseIconImage = await getBaseIconImage("windows-or-linux"); - const updateAvailableImage = await generateUpdateAvailableImages(baseIconImage, "windows-or-linux"); + + const updateAvailableTemplateImage = await generateImageWithSvg(baseIconTemplateImage, "macos", noticeFile); + const updateAvailableImage = await generateImageWithSvg(baseIconImage, "windows-or-linux", noticeFile); + + const checkingForUpdatesTemplateImage = await generateImageWithSvg(baseIconTemplateImage, "macos", spinnerFile); + const checkingForUpdatesImage = await generateImageWithSvg(baseIconImage, "windows-or-linux", spinnerFile); await Promise.all([ // Templates are for macOS only generateImages(baseIconTemplateImage, size, "trayIconTemplate"), generateImages(updateAvailableTemplateImage, size, "trayIconUpdateAvailableTemplate"), + generateImages(updateAvailableTemplateImage, size, "trayIconUpdateAvailableTemplate"), + generateImages(checkingForUpdatesTemplateImage, size, "trayIconCheckingForUpdatesTemplate"), // Non-templates are for windows and linux generateImages(baseIconImage, size, "trayIcon"), generateImages(updateAvailableImage, size, "trayIconUpdateAvailable"), + generateImages(checkingForUpdatesImage, size, "trayIconCheckingForUpdates"), ]); console.log("Generated all images"); diff --git a/build/tray/trayIconCheckingForUpdates.png b/build/tray/trayIconCheckingForUpdates.png new file mode 100644 index 0000000000..4e18a7ae18 Binary files /dev/null and b/build/tray/trayIconCheckingForUpdates.png differ diff --git a/build/tray/trayIconCheckingForUpdates@2x.png b/build/tray/trayIconCheckingForUpdates@2x.png new file mode 100644 index 0000000000..fb0b4788ff Binary files /dev/null and b/build/tray/trayIconCheckingForUpdates@2x.png differ diff --git a/build/tray/trayIconCheckingForUpdates@3x.png b/build/tray/trayIconCheckingForUpdates@3x.png new file mode 100644 index 0000000000..30a112d7a1 Binary files /dev/null and b/build/tray/trayIconCheckingForUpdates@3x.png differ diff --git a/build/tray/trayIconCheckingForUpdates@4x.png b/build/tray/trayIconCheckingForUpdates@4x.png new file mode 100644 index 0000000000..3d113af086 Binary files /dev/null and b/build/tray/trayIconCheckingForUpdates@4x.png differ diff --git a/build/tray/trayIconCheckingForUpdatesTemplate.png b/build/tray/trayIconCheckingForUpdatesTemplate.png new file mode 100644 index 0000000000..de4fada91a Binary files /dev/null and b/build/tray/trayIconCheckingForUpdatesTemplate.png differ diff --git a/build/tray/trayIconCheckingForUpdatesTemplate@2x.png b/build/tray/trayIconCheckingForUpdatesTemplate@2x.png new file mode 100644 index 0000000000..9ec4654367 Binary files /dev/null and b/build/tray/trayIconCheckingForUpdatesTemplate@2x.png differ diff --git a/build/tray/trayIconCheckingForUpdatesTemplate@3x.png b/build/tray/trayIconCheckingForUpdatesTemplate@3x.png new file mode 100644 index 0000000000..070ea521e4 Binary files /dev/null and b/build/tray/trayIconCheckingForUpdatesTemplate@3x.png differ diff --git a/build/tray/trayIconCheckingForUpdatesTemplate@4x.png b/build/tray/trayIconCheckingForUpdatesTemplate@4x.png new file mode 100644 index 0000000000..08b3c0a998 Binary files /dev/null and b/build/tray/trayIconCheckingForUpdatesTemplate@4x.png differ diff --git a/src/behaviours/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap b/src/behaviours/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap index 74307f3b53..2cc8acffca 100644 --- a/src/behaviours/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap +++ b/src/behaviours/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap @@ -187,17 +187,7 @@ exports[`extension special characters in page registrations renders 1`] = `
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
-
- Checking for updates... -
-
-
+ />
-
-
- Downloading version some-version... -
-
-
-
+ />
-
-
- Download of update failed -
-
-
+ />
-
-
- some-version is available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
-
- Checking for updates... -
-
-
+ />
-
-
- Downloading version some-version... -
-
-
-
+ />
-
-
- Download of update failed -
-
-
+ />
-
-
- some-version is available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
{ let applicationBuilder: ApplicationBuilder; let checkForPlatformUpdatesMock: AsyncFnMock; let downloadPlatformUpdateMock: AsyncFnMock; let showApplicationWindowMock: jest.Mock; - let trayIconPaths: TrayIconPaths; beforeEach(() => { applicationBuilder = getApplicationBuilder(); @@ -46,7 +43,6 @@ describe("installing update using tray", () => { mainDi.override(electronUpdaterIsActiveInjectable, () => true); mainDi.override(publishIsConfiguredInjectable, () => true); - trayIconPaths = mainDi.inject(trayIconPathsInjectable); }); }); @@ -61,10 +57,6 @@ describe("installing update using tray", () => { expect(rendered.baseElement).toMatchSnapshot(); }); - it("should use the normal tray icon", () => { - expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal); - }); - it("user cannot install update yet", () => { expect(applicationBuilder.tray.get("install-update")).toBeNull(); }); @@ -80,10 +72,6 @@ describe("installing update using tray", () => { expect(showApplicationWindowMock).not.toHaveBeenCalled(); }); - it("should still use the normal tray icon", () => { - expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal); - }); - it("user cannot check for updates again", () => { expect( applicationBuilder.tray.get("check-for-updates")?.enabled, @@ -117,10 +105,6 @@ describe("installing update using tray", () => { expect(showApplicationWindowMock).toHaveBeenCalled(); }); - it("should still use the normal tray icon", () => { - expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal); - }); - it("user cannot install update", () => { expect(applicationBuilder.tray.get("install-update")).toBeNull(); }); @@ -156,10 +140,6 @@ describe("installing update using tray", () => { expect(showApplicationWindowMock).toHaveBeenCalled(); }); - it("should use the update available icon", () => { - expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.updateAvailable); - }); - it("user cannot check for updates again yet", () => { expect( applicationBuilder.tray.get("check-for-updates")?.enabled, @@ -199,10 +179,6 @@ describe("installing update using tray", () => { ).toBeNull(); }); - it("should revert to use the normal tray icon", () => { - expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal); - }); - it("user can check for updates again", () => { expect( applicationBuilder.tray.get("check-for-updates")?.enabled, @@ -231,10 +207,6 @@ describe("installing update using tray", () => { ).toBe("Install update some-version"); }); - it("should use the update available icon", () => { - expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.updateAvailable); - }); - it("user can check for updates again", () => { expect( applicationBuilder.tray.get("check-for-updates")?.enabled, diff --git a/src/behaviours/application-update/installing-update.test.ts b/src/behaviours/application-update/installing-update.test.ts index 666dbc1e4c..2e10403ed8 100644 --- a/src/behaviours/application-update/installing-update.test.ts +++ b/src/behaviours/application-update/installing-update.test.ts @@ -16,7 +16,8 @@ import type { DownloadPlatformUpdate } from "../../main/application-update/downl import downloadPlatformUpdateInjectable from "../../main/application-update/download-platform-update/download-platform-update.injectable"; import setUpdateOnQuitInjectable from "../../main/electron-app/features/set-update-on-quit.injectable"; import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable"; -import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time"; +import { useFakeTime } from "../../common/test-utils/use-fake-time"; +import staticFilesDirectoryInjectable from "../../common/vars/static-files-directory.injectable"; describe("installing update", () => { let applicationBuilder: ApplicationBuilder; @@ -36,6 +37,8 @@ describe("installing update", () => { downloadPlatformUpdateMock = asyncFn(); setUpdateOnQuitMock = jest.fn(); + mainDi.override(staticFilesDirectoryInjectable, () => "/some-static-files-directory"); + mainDi.override(setUpdateOnQuitInjectable, () => setUpdateOnQuitMock); mainDi.override( @@ -65,13 +68,21 @@ describe("installing update", () => { beforeEach(async () => { rendered = await applicationBuilder.render(); - processCheckingForUpdates = applicationBuilder.dis.mainDi.inject(processCheckingForUpdatesInjectable); + processCheckingForUpdates = applicationBuilder.dis.mainDi.inject( + processCheckingForUpdatesInjectable, + ); }); it("renders", () => { expect(rendered.baseElement).toMatchSnapshot(); }); + it("shows normal tray icon", () => { + expect(applicationBuilder.tray.getIconPath()).toBe( + "/some-static-files-directory/icons/trayIconTemplate.png", + ); + }); + describe("when user checks for updates", () => { let processCheckingForUpdatesPromise: Promise; @@ -86,8 +97,10 @@ describe("installing update", () => { ); }); - it("notifies the user that checking for updates is happening", () => { - expect(rendered.getByTestId("app-update-checking")).toBeInTheDocument(); + it("shows tray icon for checking for updates", () => { + expect(applicationBuilder.tray.getIconPath()).toBe( + "/some-static-files-directory/icons/trayIconCheckingForUpdatesTemplate.png", + ); }); it("renders", () => { @@ -103,8 +116,10 @@ describe("installing update", () => { await processCheckingForUpdatesPromise; }); - it("notifies the user", () => { - expect(rendered.getByTestId("app-update-not-available")).toBeInTheDocument(); + it("shows tray icon for normal", () => { + expect(applicationBuilder.tray.getIconPath()).toBe( + "/some-static-files-directory/icons/trayIconTemplate.png", + ); }); it("does not start downloading update", () => { @@ -114,12 +129,6 @@ describe("installing update", () => { it("renders", () => { expect(rendered.baseElement).toMatchSnapshot(); }); - - it("when 5 seconds elapses, clears the notification to the user", () => { - advanceFakeTime(6000); - - expect(rendered.getByTestId("app-update-idle")).toBeInTheDocument(); - }); }); describe("when new update is discovered", () => { @@ -136,8 +145,10 @@ describe("installing update", () => { expect(downloadPlatformUpdateMock).toHaveBeenCalled(); }); - it("notifies the user that download is happening", () => { - expect(rendered.getByTestId("app-update-downloading")).toBeInTheDocument(); + it("still shows tray icon for downloading", () => { + expect(applicationBuilder.tray.getIconPath()).toBe( + "/some-static-files-directory/icons/trayIconCheckingForUpdatesTemplate.png", + ); }); it("renders", () => { @@ -153,8 +164,10 @@ describe("installing update", () => { expect(quitAndInstallUpdateMock).not.toHaveBeenCalled(); }); - it("notifies the user about failed download", () => { - expect(rendered.getByTestId("app-update-download-failed")).toBeInTheDocument(); + it("still shows normal tray icon", () => { + expect(applicationBuilder.tray.getIconPath()).toBe( + "/some-static-files-directory/icons/trayIconTemplate.png", + ); }); it("renders", () => { @@ -171,8 +184,10 @@ describe("installing update", () => { expect(quitAndInstallUpdateMock).not.toHaveBeenCalled(); }); - it("notifies the user about successful download", () => { - expect(rendered.getByTestId("app-update-available")).toBeInTheDocument(); + it("shows tray icon for update being available", () => { + expect(applicationBuilder.tray.getIconPath()).toBe( + "/some-static-files-directory/icons/trayIconUpdateAvailableTemplate.png", + ); }); it("renders", () => { diff --git a/src/behaviours/extensions/__snapshots__/navigation-using-application-menu.test.ts.snap b/src/behaviours/extensions/__snapshots__/navigation-using-application-menu.test.ts.snap index 7abb5b3bf5..aafe097eb1 100644 --- a/src/behaviours/extensions/__snapshots__/navigation-using-application-menu.test.ts.snap +++ b/src/behaviours/extensions/__snapshots__/navigation-using-application-menu.test.ts.snap @@ -187,17 +187,7 @@ exports[`extensions - navigation using application menu renders 1`] = `
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
diff --git a/src/behaviours/welcome/__snapshots__/navigation-using-application-menu.test.ts.snap b/src/behaviours/welcome/__snapshots__/navigation-using-application-menu.test.ts.snap index d7575b76a7..43f0bf8522 100644 --- a/src/behaviours/welcome/__snapshots__/navigation-using-application-menu.test.ts.snap +++ b/src/behaviours/welcome/__snapshots__/navigation-using-application-menu.test.ts.snap @@ -187,17 +187,7 @@ exports[`welcome - navigation using application menu renders 1`] = `
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
-
-
- No new updates available -
-
-
+ />
{ 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/checking-for-updates-tray-icon.injectable.ts b/src/main/application-update/tray-icons/checking-for-updates-tray-icon.injectable.ts new file mode 100644 index 0000000000..5d22c98bba --- /dev/null +++ b/src/main/application-update/tray-icons/checking-for-updates-tray-icon.injectable.ts @@ -0,0 +1,34 @@ +/** + * 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 updatesAreBeingDiscoveredInjectable from "../../../common/application-update/updates-are-being-discovered/updates-are-being-discovered.injectable"; +import updateIsBeingDownloadedInjectable from "../../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable"; + +const checkingForUpdatesTrayIconInjectable = getInjectable({ + id: "checking-for-updates-tray-icon", + + instantiate: (di) => { + const getTrayIconPath = di.inject(getTrayIconPathInjectable); + const updatesAreBeingDiscovered = di.inject(updatesAreBeingDiscoveredInjectable); + const updateIsBeingDownloaded = di.inject(updateIsBeingDownloadedInjectable); + + return { + iconPath: getTrayIconPath("checking-for-updates"), + priority: 1, + shouldBeShown: computed( + () => + updatesAreBeingDiscovered.value.get() || + updateIsBeingDownloaded.value.get(), + ), + }; + }, + + injectionToken: trayIconInjectionToken, +}); + +export default checkingForUpdatesTrayIconInjectable; 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..7f097c0cbb --- /dev/null +++ b/src/main/application-update/tray-icons/update-is-ready-to-be-installed-tray-icon.injectable.ts @@ -0,0 +1,27 @@ +/** + * 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 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: updateIsReadyToBeInstalled, + }; + }, + + 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/icon/arrow-spinner.svg b/src/renderer/components/icon/arrow-spinner.svg new file mode 100644 index 0000000000..413b93f3a2 --- /dev/null +++ b/src/renderer/components/icon/arrow-spinner.svg @@ -0,0 +1,6 @@ + + + + diff --git a/src/renderer/components/status-bar/auto-update-component.tsx b/src/renderer/components/status-bar/auto-update-component.tsx deleted file mode 100644 index 0f6e69c086..0000000000 --- a/src/renderer/components/status-bar/auto-update-component.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { withInjectables } from "@ogre-tools/injectable-react"; -import { observer } from "mobx-react"; -import assert from "assert"; -import React, { useState } from "react"; -import { Spinner } from "../spinner"; -import type { ProgressOfUpdateDownload } from "../../../common/application-update/progress-of-update-download/progress-of-update-download.injectable"; -import progressOfUpdateDownloadInjectable from "../../../common/application-update/progress-of-update-download/progress-of-update-download.injectable"; -import type { DiscoveredUpdateVersion } from "../../../common/application-update/discovered-update-version/discovered-update-version.injectable"; -import discoveredUpdateVersionInjectable from "../../../common/application-update/discovered-update-version/discovered-update-version.injectable"; -import type { UpdateIsBeingDownloaded } from "../../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable"; -import updateIsBeingDownloadedInjectable from "../../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable"; -import type { UpdatesAreBeingDiscovered } from "../../../common/application-update/updates-are-being-discovered/updates-are-being-discovered.injectable"; -import updatesAreBeingDiscoveredInjectable from "../../../common/application-update/updates-are-being-discovered/updates-are-being-discovered.injectable"; -import { reactiveNow } from "../../../common/utils/reactive-now/reactive-now"; - -interface Dependencies { - progressOfUpdateDownload: ProgressOfUpdateDownload; - discoveredVersionState: DiscoveredUpdateVersion; - downloadingUpdateState: UpdateIsBeingDownloaded; - checkingForUpdatesState: UpdatesAreBeingDiscovered; -} - -interface EndNoteProps { - version?: string; - note: (version: string) => JSX.Element; -} - -const EndNote = observer(({ version, note }: EndNoteProps) => { - const [start] = useState(Date.now()); - - if (start + 5000 <= reactiveNow()) { - return idle(); - } - - return note(version ?? ""); -}); - -const checking = () => ( - <> - -
Checking for updates...
- -); - -const available = (version: string) =>
{`${version ?? "Update"} is available`}
; - -const notAvailable = () =>
No new updates available
; - -const downloading = (version: string) => { - return ( - <> -
{`Downloading version ${version}...`}
- - - ); -}; - -const downloadFailed = (errMsg: string) =>
{errMsg}
; - -const idle = () =>
; - - -export const NonInjectedAutoUpdateComponent = observer(({ - progressOfUpdateDownload, - discoveredVersionState, - downloadingUpdateState, - checkingForUpdatesState, -}: Dependencies) => { - const discoveredVersion = discoveredVersionState.value.get(); - - const { failed } = progressOfUpdateDownload.value.get(); - - if (downloadingUpdateState.value.get()) { - - assert(discoveredVersion); - - return downloading(discoveredVersion.version); - } - - if (checkingForUpdatesState.value.get()) { - return checking(); - } - - if ( discoveredVersion) { - return ; - } - - if ( failed ) { - return ; - } - - return ; -}); - -export const AutoUpdateComponent = withInjectables(NonInjectedAutoUpdateComponent, { - getProps: (di, props) => ({ - progressOfUpdateDownload: di.inject(progressOfUpdateDownloadInjectable), - discoveredVersionState: di.inject(discoveredUpdateVersionInjectable), - downloadingUpdateState: di.inject(updateIsBeingDownloadedInjectable), - checkingForUpdatesState: di.inject(updatesAreBeingDiscoveredInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/status-bar/auto-update-status-bar-item.injectable.ts b/src/renderer/components/status-bar/auto-update-status-bar-item.injectable.ts deleted file mode 100644 index de8f1518e8..0000000000 --- a/src/renderer/components/status-bar/auto-update-status-bar-item.injectable.ts +++ /dev/null @@ -1,22 +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 { computed } from "mobx"; -import { AutoUpdateComponent } from "./auto-update-component"; -import { statusBarItemInjectionToken } from "./status-bar-item-injection-token"; - -const autoUpdateStatusBarItemInjectable = getInjectable({ - id: "auto-update-status-bar-item", - - instantiate: () => ({ - component: AutoUpdateComponent, - position: "left" as const, - visible: computed(() => true), - }), - - injectionToken: statusBarItemInjectionToken, -}); - -export default autoUpdateStatusBarItemInjectable; 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) => {