From 8ada3c7505ef46343b0b84318f7c601609e3cf9b Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 28 Apr 2022 09:31:57 -0700 Subject: [PATCH] Compute Tray Icon on the fly instead of at build (#5157) --- build/build_tray_icon.ts | 55 -------------- build/tray/trayIconDarkTemplate.png | Bin 422 -> 0 bytes build/tray/trayIconDarkTemplate@2x.png | Bin 925 -> 0 bytes build/tray/trayIconDarkTemplate@3x.png | Bin 1496 -> 0 bytes build/tray/trayIconTemplate.png | Bin 460 -> 0 bytes build/tray/trayIconTemplate@2x.png | Bin 1107 -> 0 bytes build/tray/trayIconTemplate@3x.png | Bin 1821 -> 0 bytes package.json | 5 +- src/common/utils/collection-functions.ts | 11 +++ src/main/index.ts | 2 +- src/main/tray/tray.ts | 88 +++++++++++++++++------ 11 files changed, 84 insertions(+), 77 deletions(-) delete mode 100644 build/build_tray_icon.ts delete mode 100644 build/tray/trayIconDarkTemplate.png delete mode 100644 build/tray/trayIconDarkTemplate@2x.png delete mode 100644 build/tray/trayIconDarkTemplate@3x.png delete mode 100644 build/tray/trayIconTemplate.png delete mode 100644 build/tray/trayIconTemplate@2x.png delete mode 100644 build/tray/trayIconTemplate@3x.png 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 d0a4bdce20d5ed491c75a70108b6dff15ff999b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 422 zcmV;X0a^ZuP)RRPJ1fshTP2c0JpVcV(cg6)`0jSuo!yy#XOiT{7G#`)L(l|gpqBi$ z#bEyJY>pRT26ABsbip+!bMI2@fO9&0f;q>hpdyDofod4Z78rnakjds}unH!ezw!Sl zkrdYFLLD^ZfFp1aq~na;c>}ykcq`xzJTNtj>;??MB^ZG#&;!c})y^g76R^i<+$@bY z*NhhEfZc?XoXUTIcd$vvk{mLPq+tRVpK#$;97~d_U*gYfmlt7lIPnnM4BM^Fv|2&B zmVO~bkHU;7)R4mUDWnN{Pk}Re($sGl|MOJ#gvTES47}B%R6Ve|1ocjH8xC_4W#MJ*l1~_d!sE{V=FhQb{cE_FYS+e+;9O3 QZvX%Q07*qoM6N<$f_8DU@Bjb+ diff --git a/build/tray/trayIconDarkTemplate@2x.png b/build/tray/trayIconDarkTemplate@2x.png deleted file mode 100644 index 46615aa5bf4b72a70c980921669f77c47e985952..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 925 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: 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 {