diff --git a/build/build_tray_icon.ts b/build/build_tray_icon.ts deleted file mode 100644 index 0850011815..0000000000 --- a/build/build_tray_icon.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import path from "path"; -import sharp from "sharp"; -import jsdom from "jsdom"; -import fs from "fs-extra"; - -export async function generateTrayIcon( - { - outputFilename = "trayIcon", - svgIconPath = path.resolve(__dirname, "../src/renderer/components/icon/logo-lens.svg"), - outputFolder = path.resolve(__dirname, "./tray"), - dpiSuffix = "2x", - pixelSize = 32, - shouldUseDarkColors = false, // managed by electron.nativeTheme.shouldUseDarkColors - } = {}) { - outputFilename += `${shouldUseDarkColors ? "Dark" : ""}Template`; // e.g. output trayIconDarkTemplate@2x.png - dpiSuffix = dpiSuffix !== "1x" ? `@${dpiSuffix}` : ""; - const pngIconDestPath = path.resolve(outputFolder, `${outputFilename}${dpiSuffix}.png`); - - try { - // Modify .SVG colors - const trayIconColor = shouldUseDarkColors ? "black" : "white"; - const svgDom = await jsdom.JSDOM.fromFile(svgIconPath); - const svgRoot = svgDom.window.document.body.getElementsByTagName("svg")[0]; - - svgRoot.innerHTML += ``; - const svgIconBuffer = Buffer.from(svgRoot.outerHTML); - // Resize and convert to .PNG - const pngIconBuffer: Buffer = await sharp(svgIconBuffer) - .resize({ width: pixelSize, height: pixelSize }) - .png() - .toBuffer(); - - // Save icon - await fs.writeFile(pngIconDestPath, pngIconBuffer); - console.info(`[DONE]: Tray icon saved at "${pngIconDestPath}"`); - } catch (err) { - console.error(`[ERROR]: ${err}`); - } -} - -// Run -const iconSizes: Record = { - "1x": 16, - "2x": 32, - "3x": 48, -}; - -Object.entries(iconSizes).forEach(([dpiSuffix, pixelSize]) => { - generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: false }); - generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: true }); -}); diff --git a/build/tray/trayIconDarkTemplate.png b/build/tray/trayIconDarkTemplate.png deleted file mode 100644 index d0a4bdce20..0000000000 Binary files a/build/tray/trayIconDarkTemplate.png and /dev/null differ diff --git a/build/tray/trayIconDarkTemplate@2x.png b/build/tray/trayIconDarkTemplate@2x.png deleted file mode 100644 index 46615aa5bf..0000000000 Binary files a/build/tray/trayIconDarkTemplate@2x.png and /dev/null differ diff --git a/build/tray/trayIconDarkTemplate@3x.png b/build/tray/trayIconDarkTemplate@3x.png deleted file mode 100644 index 0c018f3ed3..0000000000 Binary files a/build/tray/trayIconDarkTemplate@3x.png and /dev/null differ diff --git a/build/tray/trayIconTemplate.png b/build/tray/trayIconTemplate.png deleted file mode 100644 index 16ada5ad31..0000000000 Binary files a/build/tray/trayIconTemplate.png and /dev/null differ diff --git a/build/tray/trayIconTemplate@2x.png b/build/tray/trayIconTemplate@2x.png deleted file mode 100644 index 73c40de994..0000000000 Binary files a/build/tray/trayIconTemplate@2x.png and /dev/null differ diff --git a/build/tray/trayIconTemplate@3x.png b/build/tray/trayIconTemplate@3x.png deleted file mode 100644 index d7a39b9260..0000000000 Binary files a/build/tray/trayIconTemplate@3x.png and /dev/null differ diff --git a/package.json b/package.json index 6be5a730ab..aadcaa981d 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,9 @@ "rpm", "AppImage" ], + "asarUnpack": [ + "**/node_modules/sharp/**" + ], "extraResources": [ { "from": "binaries/client/linux/${arch}/kubectl", @@ -260,6 +263,7 @@ "rfc6902": "^4.0.2", "selfsigned": "^2.0.1", "semver": "^7.3.7", + "sharp": "^0.30.3", "shell-env": "^3.0.1", "spdy": "^4.0.2", "tar": "^6.1.11", @@ -388,7 +392,6 @@ "react-window": "^1.8.6", "sass": "^1.49.11", "sass-loader": "^12.6.0", - "sharp": "^0.30.3", "style-loader": "^3.3.1", "tailwindcss": "^3.0.23", "tar-stream": "^2.2.0", diff --git a/src/common/utils/collection-functions.ts b/src/common/utils/collection-functions.ts index 0329a52379..76646601eb 100644 --- a/src/common/utils/collection-functions.ts +++ b/src/common/utils/collection-functions.ts @@ -48,6 +48,17 @@ export function getOrInsertWith(map: Map, key: K, builder: () => V): return map.get(key); } +/** + * Like {@link getOrInsertWith} but the builder is async and will be awaited before inserting into the map + */ +export async function getOrInsertWithAsync(map: Map, key: K, asyncBuilder: () => Promise): Promise { + if (!map.has(key)) { + map.set(key, await asyncBuilder()); + } + + return map.get(key); +} + /** * Set the value associated with `key` iff there was not a previous value * @param map The map to interact with diff --git a/src/main/index.ts b/src/main/index.ts index f6c0b1bbc0..5b4f965a93 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -305,7 +305,7 @@ async function main(di: DiContainer) { onQuitCleanup.push( initMenu(applicationMenuItems), - initTray(windowManager, trayMenuItems, navigateToPreferences), + await initTray(windowManager, trayMenuItems, navigateToPreferences), () => ShellSession.cleanup(), ); diff --git a/src/main/tray/tray.ts b/src/main/tray/tray.ts index bef95610fd..af129ac27a 100644 --- a/src/main/tray/tray.ts +++ b/src/main/tray/tray.ts @@ -3,19 +3,23 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import path from "path"; import packageInfo from "../../../package.json"; -import { Menu, Tray } from "electron"; +import type { NativeImage } from "electron"; +import { Menu, nativeImage, nativeTheme, Tray } from "electron"; import type { IComputedValue } from "mobx"; import { autorun } from "mobx"; import { showAbout } from "../menu/menu"; import { checkForUpdates, isAutoUpdateEnabled } from "../app-updater"; import type { WindowManager } from "../window-manager"; import logger from "../logger"; -import { isDevelopment, isWindows, productName, staticFilesDirectory } from "../../common/vars"; +import { isWindows, productName } from "../../common/vars"; import { exitApp } from "../exit-app"; -import { toJS } from "../../common/utils"; +import type { Disposer } from "../../common/utils"; +import { base64, disposer, getOrInsertWithAsync, toJS } from "../../common/utils"; import type { TrayMenuRegistration } from "./tray-menu-registration"; +import sharp from "sharp"; +import LogoLens from "../../renderer/components/icon/logo-lens.svg"; +import { JSDOM } from "jsdom"; const TRAY_LOG_PREFIX = "[TRAY]"; @@ -23,25 +27,69 @@ const TRAY_LOG_PREFIX = "[TRAY]"; // note: instance of Tray should be saved somewhere, otherwise it disappears export let tray: Tray; -export function getTrayIcon(): string { - return path.resolve( - staticFilesDirectory, - isDevelopment ? "../build/tray" : "icons", // copied within electron-builder extras - "trayIconTemplate.png", - ); +interface CreateTrayIconArgs { + shouldUseDarkColors: boolean; + size: number; + sourceSvg: string; } -export function initTray( +const trayIcons = new Map(); + +async function createTrayIcon({ shouldUseDarkColors, size, sourceSvg }: CreateTrayIconArgs): Promise { + return getOrInsertWithAsync(trayIcons, shouldUseDarkColors, async () => { + const trayIconColor = shouldUseDarkColors ? "white" : "black"; // Invert to show contrast + const parsedSvg = base64.decode(sourceSvg.split("base64,")[1]); + const svgDom = new JSDOM(`${parsedSvg}`); + const svgRoot = svgDom.window.document.body.getElementsByTagName("svg")[0]; + + svgRoot.innerHTML += ``; + + const iconBuffer = await sharp(Buffer.from(svgRoot.outerHTML)) + .resize({ width: size, height: size }) + .png() + .toBuffer(); + + return nativeImage.createFromBuffer(iconBuffer); + }); +} + +function createCurrentTrayIcon() { + return createTrayIcon({ + shouldUseDarkColors: nativeTheme.shouldUseDarkColors, + size: 16, + sourceSvg: LogoLens, + }); +} + +function watchShouldUseDarkColors(tray: Tray): Disposer { + let prevShouldUseDarkColors = nativeTheme.shouldUseDarkColors; + const onUpdated = () => { + if (prevShouldUseDarkColors !== nativeTheme.shouldUseDarkColors) { + prevShouldUseDarkColors = nativeTheme.shouldUseDarkColors; + createCurrentTrayIcon() + .then(img => tray.setImage(img)); + } + }; + + nativeTheme.on("updated", onUpdated); + + return () => nativeTheme.off("updated", onUpdated); +} + +export async function initTray( windowManager: WindowManager, trayMenuItems: IComputedValue, navigateToPreferences: () => void, -) { - const icon = getTrayIcon(); +): Promise { + const icon = await createCurrentTrayIcon(); + const dispose = disposer(); tray = new Tray(icon); tray.setToolTip(packageInfo.description); tray.setIgnoreDoubleClickEvents(true); + dispose.push(watchShouldUseDarkColors(tray)); + if (isWindows) { tray.on("click", () => { windowManager @@ -50,7 +98,7 @@ export function initTray( }); } - const disposers = [ + dispose.push( autorun(() => { try { const menu = createTrayMenu(windowManager, toJS(trayMenuItems.get()), navigateToPreferences); @@ -60,13 +108,13 @@ export function initTray( logger.error(`${TRAY_LOG_PREFIX}: building failed`, { error }); } }), - ]; + () => { + tray?.destroy(); + tray = null; + }, + ); - return () => { - disposers.forEach(disposer => disposer()); - tray?.destroy(); - tray = null; - }; + return dispose; } function getMenuItemConstructorOptions(trayItem: TrayMenuRegistration): Electron.MenuItemConstructorOptions {