mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add reactive tray icon for when update is available (#5567)
This commit is contained in:
1 parent
db65a89ee0
commit
17c7b6a1bf
32 files changed
+399
-241
No files matched your search
+10
-10
@@ -44,7 +44,7 @@ exports[`installing update using tray when started when user checks for updates
|
||||
>
|
||||
<i
|
||||
class="Icon close material interactive focusable"
|
||||
data-testid="close-notification-for-notification_13"
|
||||
data-testid="close-notification-for-notification_16"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -95,7 +95,7 @@ exports[`installing update using tray when started when user checks for updates
|
||||
>
|
||||
<i
|
||||
class="Icon close material interactive focusable"
|
||||
data-testid="close-notification-for-notification_96"
|
||||
data-testid="close-notification-for-notification_115"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -135,7 +135,7 @@ exports[`installing update using tray when started when user checks for updates
|
||||
>
|
||||
<i
|
||||
class="Icon close material interactive focusable"
|
||||
data-testid="close-notification-for-notification_99"
|
||||
data-testid="close-notification-for-notification_118"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -186,7 +186,7 @@ exports[`installing update using tray when started when user checks for updates
|
||||
>
|
||||
<i
|
||||
class="Icon close material interactive focusable"
|
||||
data-testid="close-notification-for-notification_149"
|
||||
data-testid="close-notification-for-notification_183"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -226,7 +226,7 @@ exports[`installing update using tray when started when user checks for updates
|
||||
>
|
||||
<i
|
||||
class="Icon close material interactive focusable"
|
||||
data-testid="close-notification-for-notification_152"
|
||||
data-testid="close-notification-for-notification_186"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -266,7 +266,7 @@ exports[`installing update using tray when started when user checks for updates
|
||||
>
|
||||
<i
|
||||
class="Icon close material interactive focusable"
|
||||
data-testid="close-notification-for-notification_157"
|
||||
data-testid="close-notification-for-notification_191"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -317,7 +317,7 @@ exports[`installing update using tray when started when user checks for updates
|
||||
>
|
||||
<i
|
||||
class="Icon close material interactive focusable"
|
||||
data-testid="close-notification-for-notification_215"
|
||||
data-testid="close-notification-for-notification_266"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -357,7 +357,7 @@ exports[`installing update using tray when started when user checks for updates
|
||||
>
|
||||
<i
|
||||
class="Icon close material interactive focusable"
|
||||
data-testid="close-notification-for-notification_218"
|
||||
data-testid="close-notification-for-notification_269"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -478,7 +478,7 @@ exports[`installing update using tray when started when user checks for updates
|
||||
>
|
||||
<i
|
||||
class="Icon close material interactive focusable"
|
||||
data-testid="close-notification-for-notification_48"
|
||||
data-testid="close-notification-for-notification_59"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -518,7 +518,7 @@ exports[`installing update using tray when started when user checks for updates
|
||||
>
|
||||
<i
|
||||
class="Icon close material interactive focusable"
|
||||
data-testid="close-notification-for-notification_51"
|
||||
data-testid="close-notification-for-notification_62"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
|
||||
@@ -15,12 +15,15 @@ import type { DownloadPlatformUpdate } from "../../main/application-update/downl
|
||||
import downloadPlatformUpdateInjectable from "../../main/application-update/download-platform-update/download-platform-update.injectable";
|
||||
import showApplicationWindowInjectable from "../../main/start-main-application/lens-window/show-application-window.injectable";
|
||||
import progressOfUpdateDownloadInjectable from "../../common/application-update/progress-of-update-download/progress-of-update-download.injectable";
|
||||
import type { TrayIconPaths } from "../../main/tray/tray-icon-path.injectable";
|
||||
import trayIconPathsInjectable from "../../main/tray/tray-icon-path.injectable";
|
||||
|
||||
describe("installing update using tray", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
let checkForPlatformUpdatesMock: AsyncFnMock<CheckForPlatformUpdates>;
|
||||
let downloadPlatformUpdateMock: AsyncFnMock<DownloadPlatformUpdate>;
|
||||
let showApplicationWindowMock: jest.Mock;
|
||||
let trayIconPaths: TrayIconPaths;
|
||||
|
||||
beforeEach(() => {
|
||||
applicationBuilder = getApplicationBuilder();
|
||||
@@ -44,6 +47,7 @@ describe("installing update using tray", () => {
|
||||
|
||||
mainDi.override(electronUpdaterIsActiveInjectable, () => true);
|
||||
mainDi.override(publishIsConfiguredInjectable, () => true);
|
||||
trayIconPaths = mainDi.inject(trayIconPathsInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,22 +62,29 @@ 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")).toBeUndefined();
|
||||
expect(applicationBuilder.tray.get("install-update")).toBeNull();
|
||||
});
|
||||
|
||||
describe("when user checks for updates using tray", () => {
|
||||
let processCheckingForUpdatesPromise: Promise<void>;
|
||||
|
||||
beforeEach(async () => {
|
||||
processCheckingForUpdatesPromise =
|
||||
applicationBuilder.tray.click("check-for-updates");
|
||||
processCheckingForUpdatesPromise = applicationBuilder.tray.click("check-for-updates");
|
||||
});
|
||||
|
||||
it("does not show application window yet", () => {
|
||||
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.get(),
|
||||
@@ -87,7 +98,7 @@ describe("installing update using tray", () => {
|
||||
});
|
||||
|
||||
it("user cannot install update yet", () => {
|
||||
expect(applicationBuilder.tray.get("install-update")).toBeUndefined();
|
||||
expect(applicationBuilder.tray.get("install-update")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
@@ -107,8 +118,12 @@ 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")).toBeUndefined();
|
||||
expect(applicationBuilder.tray.get("install-update")).toBeNull();
|
||||
});
|
||||
|
||||
it("user can check for updates again", () => {
|
||||
@@ -142,6 +157,10 @@ 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.get(),
|
||||
@@ -167,7 +186,7 @@ describe("installing update using tray", () => {
|
||||
});
|
||||
|
||||
it("user still cannot install update", () => {
|
||||
expect(applicationBuilder.tray.get("install-update")).toBeUndefined();
|
||||
expect(applicationBuilder.tray.get("install-update")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
@@ -182,7 +201,11 @@ describe("installing update using tray", () => {
|
||||
it("user cannot install update", () => {
|
||||
expect(
|
||||
applicationBuilder.tray.get("install-update"),
|
||||
).toBeUndefined();
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("should revert to use the normal tray icon", () => {
|
||||
expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal);
|
||||
});
|
||||
|
||||
it("user can check for updates again", () => {
|
||||
@@ -213,6 +236,10 @@ 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.get(),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
|
||||
export type UpdateChannelId = "alpha" | "beta" | "latest";
|
||||
|
||||
const latestChannel: UpdateChannel = {
|
||||
|
||||
@@ -4,12 +4,21 @@
|
||||
*/
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import type { JsonValue } from "type-fest";
|
||||
|
||||
export interface SyncBox<TValue extends JsonValue> {
|
||||
type AsJson<T> = T extends string | number | boolean | null
|
||||
? T
|
||||
: T extends Function
|
||||
? never
|
||||
: T extends Array<infer V>
|
||||
? AsJson<V>[]
|
||||
: T extends object
|
||||
? { [K in keyof T]: AsJson<T[K]> }
|
||||
: never;
|
||||
|
||||
export interface SyncBox<TValue> {
|
||||
id: string;
|
||||
value: IComputedValue<TValue>;
|
||||
set: (value: TValue) => void;
|
||||
value: IComputedValue<AsJson<TValue>>;
|
||||
set: (value: AsJson<TValue>) => void;
|
||||
}
|
||||
|
||||
export const syncBoxInjectionToken = getInjectionToken<SyncBox<any>>({
|
||||
|
||||
@@ -5,31 +5,37 @@
|
||||
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";
|
||||
import trayIconPathsInjectable from "../tray-icon-path.injectable";
|
||||
import type { TrayMenuItem } from "../tray-menu-item/tray-menu-item-injection-token";
|
||||
import { convertToElectronMenuTemplate } from "../reactive-tray-menu-items/converters";
|
||||
|
||||
const TRAY_LOG_PREFIX = "[TRAY]";
|
||||
|
||||
export interface ElectronTray {
|
||||
start(): void;
|
||||
stop(): void;
|
||||
setMenuItems(menuItems: TrayMenuItem[]): void;
|
||||
setIconPath(iconPath: string): void;
|
||||
}
|
||||
|
||||
const electronTrayInjectable = getInjectable({
|
||||
id: "electron-tray",
|
||||
|
||||
instantiate: (di) => {
|
||||
instantiate: (di): ElectronTray => {
|
||||
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);
|
||||
const trayIconPaths = di.inject(trayIconPathsInjectable);
|
||||
|
||||
let tray: Tray;
|
||||
|
||||
return {
|
||||
start: () => {
|
||||
tray = new Tray(trayIconPath);
|
||||
tray = new Tray(trayIconPaths.normal);
|
||||
|
||||
tray.setToolTip(packageJson.description);
|
||||
tray.setIgnoreDoubleClickEvents(true);
|
||||
@@ -41,21 +47,17 @@ const electronTrayInjectable = getInjectable({
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
stop: () => {
|
||||
tray.destroy();
|
||||
},
|
||||
setMenuItems: (menuItems) => {
|
||||
const template = convertToElectronMenuTemplate(menuItems);
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
|
||||
setMenuItems: (items: TrayMenuItem[]) => {
|
||||
pipeline(
|
||||
items,
|
||||
convertToElectronMenuTemplate,
|
||||
Menu.buildFromTemplate,
|
||||
|
||||
(template) => {
|
||||
tray.setContextMenu(template);
|
||||
},
|
||||
);
|
||||
tray.setContextMenu(menu);
|
||||
},
|
||||
setIconPath: (iconPath) => {
|
||||
tray.setImage(iconPath);
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -64,53 +66,3 @@ const electronTrayInjectable = getInjectable({
|
||||
});
|
||||
|
||||
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,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 { 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";
|
||||
|
||||
const reactiveTrayMenuIconInjectable = getInjectable({
|
||||
id: "reactive-tray-menu-icon",
|
||||
instantiate: (di) => {
|
||||
const discoveredUpdateVersion = di.inject(discoveredUpdateVersionInjectable);
|
||||
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);
|
||||
}
|
||||
},
|
||||
{
|
||||
fireImmediately: true,
|
||||
},
|
||||
)
|
||||
));
|
||||
},
|
||||
});
|
||||
|
||||
export default reactiveTrayMenuIconInjectable;
|
||||
@@ -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 { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
|
||||
import startTrayInjectable from "../electron-tray/start-tray.injectable";
|
||||
import reactiveTrayMenuIconInjectable from "./reactive.injectable";
|
||||
|
||||
const startReactiveTrayMenuIconInjectable = getInjectable({
|
||||
id: "start-reactive-tray-menu-icon",
|
||||
|
||||
instantiate: (di) => {
|
||||
const reactiveTrayMenuIcon = di.inject(reactiveTrayMenuIconInjectable);
|
||||
|
||||
return {
|
||||
run: async () => {
|
||||
await reactiveTrayMenuIcon.start();
|
||||
},
|
||||
|
||||
runAfter: di.inject(startTrayInjectable),
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: onLoadOfApplicationInjectionToken,
|
||||
});
|
||||
|
||||
export default startReactiveTrayMenuIconInjectable;
|
||||
@@ -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 { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
|
||||
import reactiveTrayMenuIconInjectable from "./reactive.injectable";
|
||||
|
||||
const stopReactiveTrayMenuIconInjectable = getInjectable({
|
||||
id: "stop-reactive-tray-menu-icon",
|
||||
|
||||
instantiate: (di) => {
|
||||
const reactiveTrayMenuIcon = di.inject(reactiveTrayMenuIconInjectable);
|
||||
|
||||
return {
|
||||
run: async () => {
|
||||
await reactiveTrayMenuIcon.stop();
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: beforeQuitOfBackEndInjectionToken,
|
||||
});
|
||||
|
||||
export default stopReactiveTrayMenuIconInjectable;
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { TrayMenuItem } from "../tray-menu-item/tray-menu-item-injection-token";
|
||||
|
||||
export function convertToElectronMenuTemplate(trayMenuItems: TrayMenuItem[]): Electron.MenuItemConstructorOptions[] {
|
||||
const toTrayMenuOptions = (parentId: string | null) => (
|
||||
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,
|
||||
|
||||
...(childItems.length === 0
|
||||
? {
|
||||
type: "normal",
|
||||
submenu: toTrayMenuOptions(trayMenuItem.id),
|
||||
click: trayMenuItem.click,
|
||||
}
|
||||
: {
|
||||
type: "submenu",
|
||||
submenu: toTrayMenuOptions(trayMenuItem.id),
|
||||
}),
|
||||
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return toTrayMenuOptions(null);
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
*/
|
||||
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 { reaction } from "mobx";
|
||||
import electronTrayInjectable from "../electron-tray/electron-tray.injectable";
|
||||
import trayMenuItemsInjectable from "../tray-menu-item/tray-menu-items.injectable";
|
||||
|
||||
const reactiveTrayMenuItemsInjectable = getInjectable({
|
||||
id: "reactive-tray-menu-items",
|
||||
@@ -15,9 +15,15 @@ const reactiveTrayMenuItemsInjectable = getInjectable({
|
||||
const electronTray = di.inject(electronTrayInjectable);
|
||||
const trayMenuItems = di.inject(trayMenuItemsInjectable);
|
||||
|
||||
return getStartableStoppable("reactive-tray-menu-items", () => autorun(() => {
|
||||
electronTray.setMenuItems(trayMenuItems.get());
|
||||
}));
|
||||
return getStartableStoppable("reactive-tray-menu-items", () => (
|
||||
reaction(
|
||||
() => trayMenuItems.get(),
|
||||
electronTray.setMenuItems,
|
||||
{
|
||||
fireImmediately: true,
|
||||
},
|
||||
)
|
||||
));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,21 +6,32 @@ 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";
|
||||
|
||||
const trayIconPathInjectable = getInjectable({
|
||||
id: "tray-icon-path",
|
||||
export interface TrayIconPaths {
|
||||
normal: string;
|
||||
updateAvailable: string;
|
||||
}
|
||||
|
||||
instantiate: (di) => {
|
||||
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);
|
||||
|
||||
return getAbsolutePath(
|
||||
const isMac = di.inject(isMacInjectable);
|
||||
const baseIconDirectory = getAbsolutePath(
|
||||
staticFilesDirectory,
|
||||
isDevelopment ? "../build/tray" : "icons", // copied within electron-builder extras
|
||||
"trayIconTemplate.png",
|
||||
);
|
||||
const fileSuffix = isMac ? "Template.png" : ".png";
|
||||
|
||||
return {
|
||||
normal: getAbsolutePath(baseIconDirectory, `trayIcon${fileSuffix}`),
|
||||
updateAvailable: getAbsolutePath(baseIconDirectory, `trayIconUpdateAvailable${fileSuffix}`),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default trayIconPathInjectable;
|
||||
export default trayIconPathsInjectable;
|
||||
@@ -12,8 +12,7 @@ const trayItemsInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const extensions = di.inject(mainExtensionsInjectable);
|
||||
|
||||
return computed(() =>
|
||||
extensions.get().flatMap(extension => extension.trayMenus));
|
||||
return computed(() => extensions.get().flatMap(extension => extension.trayMenus));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import packageInfo from "../../../package.json";
|
||||
import { Menu, Tray } from "electron";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { autorun } from "mobx";
|
||||
import logger from "../logger";
|
||||
import { isWindows } from "../../common/vars";
|
||||
import type { Disposer } from "../../common/utils";
|
||||
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";
|
||||
|
||||
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<TrayMenuItem[]>,
|
||||
showApplicationWindow: () => Promise<void>,
|
||||
trayIconPath: string,
|
||||
): Disposer {
|
||||
tray = new Tray(trayIconPath);
|
||||
tray.setToolTip(packageInfo.description);
|
||||
tray.setIgnoreDoubleClickEvents(true);
|
||||
|
||||
if (isWindows) {
|
||||
tray.on("click", () => {
|
||||
showApplicationWindow()
|
||||
.catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to open lens`, { error }));
|
||||
});
|
||||
}
|
||||
|
||||
return disposer(
|
||||
autorun(() => {
|
||||
try {
|
||||
const options = toTrayMenuOptions(trayMenuItems.get());
|
||||
|
||||
const menu = Menu.buildFromTemplate(options);
|
||||
|
||||
tray?.setContextMenu(menu);
|
||||
} catch (error) {
|
||||
logger.error(`${TRAY_LOG_PREFIX}: building failed`, { error });
|
||||
}
|
||||
}),
|
||||
() => {
|
||||
tray?.destroy();
|
||||
tray = null;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const toTrayMenuOptions = (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: () => {
|
||||
trayMenuItem.click?.();
|
||||
},
|
||||
}
|
||||
: {
|
||||
type: "submenu",
|
||||
submenu: _toTrayMenuOptions(trayMenuItem.id),
|
||||
}),
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return _toTrayMenuOptions(null);
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ import Spinner from "./spinner.svg";
|
||||
import Ssh from "./ssh.svg";
|
||||
import Storage from "./storage.svg";
|
||||
import Terminal from "./terminal.svg";
|
||||
import Notice from "./notice.svg";
|
||||
import User from "./user.svg";
|
||||
import Users from "./users.svg";
|
||||
import Wheel from "./wheel.svg";
|
||||
@@ -58,6 +59,7 @@ const localSvgIcons = new Map([
|
||||
["ssh", Ssh],
|
||||
["storage", Storage],
|
||||
["terminal", Terminal],
|
||||
["notice", Notice],
|
||||
["user", User],
|
||||
["users", Users],
|
||||
["wheel", Wheel],
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<svg xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:docname="notice.svg" inkscape:version="1.0 (4035a4f, 2020-05-01)" id="notice" version="1.1" viewBox="0 0 135.46666 135.46666" height="512" width="512">
|
||||
<defs id="defs2">
|
||||
<rect id="rect2785" height="134.1693" width="135.23837" y="0.26726951" x="-0.53453903" />
|
||||
<linearGradient osb:paint="solid" id="linearGradient837">
|
||||
<stop id="stop835" offset="0" style="stop-color:#000000;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview inkscape:window-maximized="0" inkscape:window-y="0" inkscape:window-x="0" inkscape:window-height="1080" inkscape:window-width="1920" inkscape:pagecheckerboard="true" units="px" showgrid="false" inkscape:document-rotation="0" inkscape:current-layer="layer1" inkscape:document-units="px" inkscape:cy="263.00246" inkscape:cx="240.74335" inkscape:zoom="2.0359339" inkscape:pageshadow="2" inkscape:pageopacity="0.0" borderopacity="1.0" bordercolor="#666666" pagecolor="#ffffff" id="base" />
|
||||
<metadata id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1">
|
||||
<ellipse ry="59.764721" rx="59.764713" cy="67.73333" cx="67.733345" id="path10" style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:15.9372;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path id="path2807" d="M 58.707956,29.522524 H 76.674473 L 76.739452,43.330427 73.23062,71.076192 62.054343,71.043701 58.740444,43.427894 Z" style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<rect y="83.204803" x="59.292759" height="14.815064" width="16.861885" id="rect2809" style="fill:#000000;fill-opacity:1;stroke:none;" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -24,12 +24,12 @@ import type { ClusterStore } from "../../../common/cluster-store/cluster-store";
|
||||
import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable";
|
||||
import currentRouteComponentInjectable from "../../routes/current-route-component.injectable";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { flatMap, compact, join, get, filter, find, map, matches } from "lodash/fp";
|
||||
import { flatMap, compact, join, get, filter, map, matches, find } from "lodash/fp";
|
||||
import preferenceNavigationItemsInjectable from "../+preferences/preferences-navigation/preference-navigation-items.injectable";
|
||||
import navigateToPreferencesInjectable from "../../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable";
|
||||
import type { MenuItemOpts } from "../../../main/menu/application-menu-items.injectable";
|
||||
import applicationMenuItemsInjectable from "../../../main/menu/application-menu-items.injectable";
|
||||
import type { MenuItem, MenuItemConstructorOptions } from "electron";
|
||||
import type { MenuItemConstructorOptions, MenuItem } from "electron";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
|
||||
import navigateToHelmChartsInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable";
|
||||
import hostedClusterInjectable from "../../../common/cluster-store/hosted-cluster.injectable";
|
||||
@@ -43,7 +43,6 @@ import { flushPromises } from "../../../common/test-utils/flush-promises";
|
||||
import type { NamespaceStore } from "../+namespaces/store";
|
||||
import namespaceStoreInjectable from "../+namespaces/store.injectable";
|
||||
import historyInjectable from "../../navigation/history.injectable";
|
||||
import type { TrayMenuItem } from "../../../main/tray/tray-menu-item/tray-menu-item-injection-token";
|
||||
import electronTrayInjectable from "../../../main/tray/electron-tray/electron-tray.injectable";
|
||||
import applicationWindowInjectable from "../../../main/start-main-application/lens-window/application-window/application-window.injectable";
|
||||
import { Notifications } from "../notifications/notifications";
|
||||
@@ -51,6 +50,8 @@ import broadcastThatRootFrameIsRenderedInjectable from "../../frames/root-frame/
|
||||
import { getDiForUnitTesting as getRendererDi } from "../../getDiForUnitTesting";
|
||||
import { getDiForUnitTesting as getMainDi } from "../../../main/getDiForUnitTesting";
|
||||
import { overrideChannels } from "../../../test-utils/channel-fakes/override-channels";
|
||||
import type { TrayMenuItem } from "../../../main/tray/tray-menu-item/tray-menu-item-injection-token";
|
||||
import trayIconPathsInjectable from "../../../main/tray/tray-icon-path.injectable";
|
||||
|
||||
type Callback = (dis: DiContainers) => void | Promise<void>;
|
||||
|
||||
@@ -65,7 +66,8 @@ export interface ApplicationBuilder {
|
||||
|
||||
tray: {
|
||||
click: (id: string) => Promise<void>;
|
||||
get: (id: string) => TrayMenuItem | undefined;
|
||||
get: (id: string) => TrayMenuItem | null;
|
||||
getIconPath: () => string;
|
||||
};
|
||||
|
||||
applicationMenu: {
|
||||
@@ -166,15 +168,22 @@ export const getApplicationBuilder = () => {
|
||||
computed(() => []),
|
||||
);
|
||||
|
||||
const iconPaths = mainDi.inject(trayIconPathsInjectable);
|
||||
|
||||
let trayMenuItemsStateFake: TrayMenuItem[];
|
||||
let trayMenuIconPath: string;
|
||||
|
||||
mainDi.override(electronTrayInjectable, () => ({
|
||||
start: () => {},
|
||||
start: () => {
|
||||
trayMenuIconPath = iconPaths.normal;
|
||||
},
|
||||
stop: () => {},
|
||||
|
||||
setMenuItems: (items) => {
|
||||
trayMenuItemsStateFake = items;
|
||||
},
|
||||
setIconPath: (path) => {
|
||||
trayMenuIconPath = path;
|
||||
},
|
||||
}));
|
||||
|
||||
let allowedResourcesState: IObservableArray<KubeResource>;
|
||||
@@ -222,9 +231,9 @@ export const getApplicationBuilder = () => {
|
||||
|
||||
tray: {
|
||||
get: (id: string) => {
|
||||
return trayMenuItemsStateFake.find(matches({ id }));
|
||||
return trayMenuItemsStateFake.find(matches({ id })) ?? null;
|
||||
},
|
||||
|
||||
getIconPath: () => trayMenuIconPath,
|
||||
click: async (id: string) => {
|
||||
const menuItem = pipeline(
|
||||
trayMenuItemsStateFake,
|
||||
|
||||
Reference in new issue
Block a user