mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Compute Tray Icon on the fly instead of at build (#5157)
This commit is contained in:
parent
51a93b8cc0
commit
8ada3c7505
@ -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 += `<style>* {fill: ${trayIconColor} !important;}</style>`;
|
|
||||||
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<string, number> = {
|
|
||||||
"1x": 16,
|
|
||||||
"2x": 32,
|
|
||||||
"3x": 48,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.entries(iconSizes).forEach(([dpiSuffix, pixelSize]) => {
|
|
||||||
generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: false });
|
|
||||||
generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: true });
|
|
||||||
});
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 422 B |
Binary file not shown.
|
Before Width: | Height: | Size: 925 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 460 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB |
@ -124,6 +124,9 @@
|
|||||||
"rpm",
|
"rpm",
|
||||||
"AppImage"
|
"AppImage"
|
||||||
],
|
],
|
||||||
|
"asarUnpack": [
|
||||||
|
"**/node_modules/sharp/**"
|
||||||
|
],
|
||||||
"extraResources": [
|
"extraResources": [
|
||||||
{
|
{
|
||||||
"from": "binaries/client/linux/${arch}/kubectl",
|
"from": "binaries/client/linux/${arch}/kubectl",
|
||||||
@ -260,6 +263,7 @@
|
|||||||
"rfc6902": "^4.0.2",
|
"rfc6902": "^4.0.2",
|
||||||
"selfsigned": "^2.0.1",
|
"selfsigned": "^2.0.1",
|
||||||
"semver": "^7.3.7",
|
"semver": "^7.3.7",
|
||||||
|
"sharp": "^0.30.3",
|
||||||
"shell-env": "^3.0.1",
|
"shell-env": "^3.0.1",
|
||||||
"spdy": "^4.0.2",
|
"spdy": "^4.0.2",
|
||||||
"tar": "^6.1.11",
|
"tar": "^6.1.11",
|
||||||
@ -388,7 +392,6 @@
|
|||||||
"react-window": "^1.8.6",
|
"react-window": "^1.8.6",
|
||||||
"sass": "^1.49.11",
|
"sass": "^1.49.11",
|
||||||
"sass-loader": "^12.6.0",
|
"sass-loader": "^12.6.0",
|
||||||
"sharp": "^0.30.3",
|
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
"tailwindcss": "^3.0.23",
|
"tailwindcss": "^3.0.23",
|
||||||
"tar-stream": "^2.2.0",
|
"tar-stream": "^2.2.0",
|
||||||
|
|||||||
@ -48,6 +48,17 @@ export function getOrInsertWith<K, V>(map: Map<K, V>, key: K, builder: () => V):
|
|||||||
return map.get(key);
|
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<K, V>(map: Map<K, V>, key: K, asyncBuilder: () => Promise<V>): Promise<V> {
|
||||||
|
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
|
* Set the value associated with `key` iff there was not a previous value
|
||||||
* @param map The map to interact with
|
* @param map The map to interact with
|
||||||
|
|||||||
@ -305,7 +305,7 @@ async function main(di: DiContainer) {
|
|||||||
|
|
||||||
onQuitCleanup.push(
|
onQuitCleanup.push(
|
||||||
initMenu(applicationMenuItems),
|
initMenu(applicationMenuItems),
|
||||||
initTray(windowManager, trayMenuItems, navigateToPreferences),
|
await initTray(windowManager, trayMenuItems, navigateToPreferences),
|
||||||
() => ShellSession.cleanup(),
|
() => ShellSession.cleanup(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -3,19 +3,23 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from "path";
|
|
||||||
import packageInfo from "../../../package.json";
|
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 type { IComputedValue } from "mobx";
|
||||||
import { autorun } from "mobx";
|
import { autorun } from "mobx";
|
||||||
import { showAbout } from "../menu/menu";
|
import { showAbout } from "../menu/menu";
|
||||||
import { checkForUpdates, isAutoUpdateEnabled } from "../app-updater";
|
import { checkForUpdates, isAutoUpdateEnabled } from "../app-updater";
|
||||||
import type { WindowManager } from "../window-manager";
|
import type { WindowManager } from "../window-manager";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
import { isDevelopment, isWindows, productName, staticFilesDirectory } from "../../common/vars";
|
import { isWindows, productName } from "../../common/vars";
|
||||||
import { exitApp } from "../exit-app";
|
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 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]";
|
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
|
// note: instance of Tray should be saved somewhere, otherwise it disappears
|
||||||
export let tray: Tray;
|
export let tray: Tray;
|
||||||
|
|
||||||
export function getTrayIcon(): string {
|
interface CreateTrayIconArgs {
|
||||||
return path.resolve(
|
shouldUseDarkColors: boolean;
|
||||||
staticFilesDirectory,
|
size: number;
|
||||||
isDevelopment ? "../build/tray" : "icons", // copied within electron-builder extras
|
sourceSvg: string;
|
||||||
"trayIconTemplate.png",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initTray(
|
const trayIcons = new Map<boolean, NativeImage>();
|
||||||
|
|
||||||
|
async function createTrayIcon({ shouldUseDarkColors, size, sourceSvg }: CreateTrayIconArgs): Promise<NativeImage> {
|
||||||
|
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(`<body>${parsedSvg}</body>`);
|
||||||
|
const svgRoot = svgDom.window.document.body.getElementsByTagName("svg")[0];
|
||||||
|
|
||||||
|
svgRoot.innerHTML += `<style>* {fill: ${trayIconColor} !important;}</style>`;
|
||||||
|
|
||||||
|
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,
|
windowManager: WindowManager,
|
||||||
trayMenuItems: IComputedValue<TrayMenuRegistration[]>,
|
trayMenuItems: IComputedValue<TrayMenuRegistration[]>,
|
||||||
navigateToPreferences: () => void,
|
navigateToPreferences: () => void,
|
||||||
) {
|
): Promise<Disposer> {
|
||||||
const icon = getTrayIcon();
|
const icon = await createCurrentTrayIcon();
|
||||||
|
const dispose = disposer();
|
||||||
|
|
||||||
tray = new Tray(icon);
|
tray = new Tray(icon);
|
||||||
tray.setToolTip(packageInfo.description);
|
tray.setToolTip(packageInfo.description);
|
||||||
tray.setIgnoreDoubleClickEvents(true);
|
tray.setIgnoreDoubleClickEvents(true);
|
||||||
|
|
||||||
|
dispose.push(watchShouldUseDarkColors(tray));
|
||||||
|
|
||||||
if (isWindows) {
|
if (isWindows) {
|
||||||
tray.on("click", () => {
|
tray.on("click", () => {
|
||||||
windowManager
|
windowManager
|
||||||
@ -50,7 +98,7 @@ export function initTray(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const disposers = [
|
dispose.push(
|
||||||
autorun(() => {
|
autorun(() => {
|
||||||
try {
|
try {
|
||||||
const menu = createTrayMenu(windowManager, toJS(trayMenuItems.get()), navigateToPreferences);
|
const menu = createTrayMenu(windowManager, toJS(trayMenuItems.get()), navigateToPreferences);
|
||||||
@ -60,13 +108,13 @@ export function initTray(
|
|||||||
logger.error(`${TRAY_LOG_PREFIX}: building failed`, { error });
|
logger.error(`${TRAY_LOG_PREFIX}: building failed`, { error });
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
];
|
() => {
|
||||||
|
|
||||||
return () => {
|
|
||||||
disposers.forEach(disposer => disposer());
|
|
||||||
tray?.destroy();
|
tray?.destroy();
|
||||||
tray = null;
|
tray = null;
|
||||||
};
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return dispose;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMenuItemConstructorOptions(trayItem: TrayMenuRegistration): Electron.MenuItemConstructorOptions {
|
function getMenuItemConstructorOptions(trayItem: TrayMenuRegistration): Electron.MenuItemConstructorOptions {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user