From eee7febfc3562d500c476282ec57a7ab0bee72b1 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 30 Sep 2020 17:30:58 +0300 Subject: [PATCH] Tray icon #833 -- part 2 Signed-off-by: Roman --- package.json | 6 +- src/main/app-updater.ts | 14 +- src/main/index.ts | 2 - src/main/menu.ts | 39 ++-- src/main/tray.ts | 59 ++++-- src/main/window-manager.ts | 35 +++- .../icon/{logo.svg => logo-kontena.svg} | 0 .../icon/{logo-full.svg => logo-lens.svg} | 0 src/renderer/components/layout/sidebar.tsx | 2 +- yarn.lock | 172 +++++++++++++++++- 10 files changed, 272 insertions(+), 57 deletions(-) rename src/renderer/components/icon/{logo.svg => logo-kontena.svg} (100%) rename src/renderer/components/icon/{logo-full.svg => logo-lens.svg} (100%) diff --git a/package.json b/package.json index 56385fc9fd..aff8b88154 100644 --- a/package.json +++ b/package.json @@ -90,8 +90,8 @@ "filter": "!**/main.js" }, { - "from": "build/icon.png", - "to": "static/icon.png" + "from": "src/renderer/components/icon/logo-lens.svg", + "to": "static/logo.svg" }, "LICENSE" ], @@ -177,6 +177,7 @@ "@types/node": "^12.12.45", "@types/proper-lockfile": "^4.1.1", "@types/react-beautiful-dnd": "^13.0.0", + "@types/sharp": "^0.26.0", "@types/tar": "^4.0.3", "array-move": "^3.0.0", "chalk": "^4.1.0", @@ -210,6 +211,7 @@ "request-promise-native": "^1.0.8", "semver": "^7.3.2", "serializr": "^2.0.3", + "sharp": "^0.26.1", "shell-env": "^3.0.0", "spdy": "^4.0.2", "tar": "^6.0.2", diff --git a/src/main/app-updater.ts b/src/main/app-updater.ts index 7b27a7c3f6..852bc3161f 100644 --- a/src/main/app-updater.ts +++ b/src/main/app-updater.ts @@ -2,18 +2,18 @@ import { autoUpdater } from "electron-updater" import logger from "./logger" export default class AppUpdater { + static defaultUpdateIntervalMs = 1000 * 60 * 60 * 24 // once a day - protected updateInterval: number = (1000 * 60 * 60 * 24) // once a day + static checkForUpdates() { + return autoUpdater.checkForUpdatesAndNotify() + } - constructor() { + constructor(protected updateInterval = AppUpdater.defaultUpdateIntervalMs) { autoUpdater.logger = logger } public start() { - setInterval(() => { - autoUpdater.checkForUpdatesAndNotify() - }, this.updateInterval) - - return autoUpdater.checkForUpdatesAndNotify() + setInterval(AppUpdater.checkForUpdates, this.updateInterval) + return AppUpdater.checkForUpdates(); } } diff --git a/src/main/index.ts b/src/main/index.ts index 638305b7b6..5ed8eb24d6 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -13,7 +13,6 @@ import { shellSync } from "./shell-sync" import { getFreePort } from "./port" import { mangleProxyEnv } from "./proxy-env" import { registerFileProtocol } from "../common/register-protocol"; -import { setupTrayIcon } from "./tray"; import { clusterStore } from "../common/cluster-store" import { userStore } from "../common/user-store"; import { workspaceStore } from "../common/workspace-store"; @@ -77,7 +76,6 @@ async function main() { // create window manager and open app windowManager = new WindowManager(proxyPort); - trayIcon = await setupTrayIcon(); } app.on("ready", main); diff --git a/src/main/menu.ts b/src/main/menu.ts index 1b0f3434d7..0f12192c0b 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -14,6 +14,22 @@ export function initMenu(windowManager: WindowManager) { }); } +export function showAbout(browserWindow: BrowserWindow) { + const appInfo = [ + `${appName}: ${app.getVersion()}`, + `Electron: ${process.versions.electron}`, + `Chrome: ${process.versions.chrome}`, + `Copyright 2020 Mirantis, Inc.`, + ] + dialog.showMessageBoxSync(browserWindow, { + title: `${isWindows ? " ".repeat(2) : ""}${appName}`, + type: "info", + buttons: ["Close"], + message: `Lens`, + detail: appInfo.join("\r\n") + }) +} + export function buildMenu(windowManager: WindowManager) { function ignoreOnMac(menuItems: MenuItemConstructorOptions[]) { if (isMac) return []; @@ -31,26 +47,7 @@ export function buildMenu(windowManager: WindowManager) { function navigate(url: string) { logger.info(`[MENU]: navigating to ${url}`); - windowManager.navigate({ - channel: "menu:navigate", - url: url, - }) - } - - function showAbout(browserWindow: BrowserWindow) { - const appInfo = [ - `${appName}: ${app.getVersion()}`, - `Electron: ${process.versions.electron}`, - `Chrome: ${process.versions.chrome}`, - `Copyright 2020 Mirantis, Inc.`, - ] - dialog.showMessageBoxSync(browserWindow, { - title: `${isWindows ? " ".repeat(2) : ""}${appName}`, - type: "info", - buttons: ["Close"], - message: `Lens`, - detail: appInfo.join("\r\n") - }) + windowManager.navigate(url); } const mt: MenuItemConstructorOptions[] = []; @@ -162,7 +159,7 @@ export function buildMenu(windowManager: WindowManager) { label: 'Reload', accelerator: 'CmdOrCtrl+R', click() { - windowManager.reload({ channel: "menu:reload" }); + windowManager.reload(); } }, { role: 'toggleDevTools' }, diff --git a/src/main/tray.ts b/src/main/tray.ts index 05b23a5f27..11bee0016a 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -1,23 +1,60 @@ import path from "path" -import { app, Menu, nativeImage, Tray } from "electron" -import { isDevelopment } from "../common/vars"; +import sharp from "sharp"; import packageInfo from "../../package.json" +import { app, dialog, Menu, nativeImage, Tray } from "electron" +import { isDevelopment, isMac } from "../common/vars"; +import { WindowManager } from "./window-manager"; +import { showAbout } from "./menu"; +import AppUpdater from "./app-updater"; +import { preferencesURL } from "../renderer/components/+preferences/preferences.route"; -export async function setupTrayIcon() { +export const trayIcon = isDevelopment + ? path.resolve(__static, "../src/renderer/components/icon/logo-lens.svg") + : path.resolve(__static, "logo.svg") // electron-builder's extraResources + +export async function setupTrayIcon(windowManager: WindowManager) { await app.whenReady(); - const iconPath = path.resolve(__static, isDevelopment ? "../build" : "", "icon.png"); + const iconSize = isMac ? 16 : 32; // todo: verify on windows/linux + const pngIcon = await sharp(trayIcon).png().toBuffer(); + const icon = nativeImage.createFromBuffer(pngIcon).resize({ + width: iconSize, + height: iconSize + }); - // todo: https://www.electronjs.org/docs/api/native-image#high-resolution-image - const icon = nativeImage.createFromPath(iconPath).resize({ width: 16, height: 16 }); - - const appTray = new Tray(icon); + const appTray = new Tray(icon) appTray.setToolTip(packageInfo.description) - appTray.setIgnoreDoubleClickEvents(true) + appTray.setIgnoreDoubleClickEvents(true); + // note: browserWindow not available within menuItem.click() as argument[1] when app is not focused / hidden const trayMenu = Menu.buildFromTemplate([ - { label: 'Item1', type: 'radio' }, - { label: 'Item2', type: 'radio' } + { + label: "About Lens", + click() { + windowManager.bringToTop(); + showAbout(windowManager.mainView); + }, + }, + { + label: "Preferences", + click() { + windowManager.bringToTop(); + windowManager.navigate(preferencesURL()); + }, + }, + { + label: "Check for updates", + async click() { + const result = await AppUpdater.checkForUpdates(); + if (!result) { + windowManager.bringToTop(); + dialog.showMessageBoxSync({ + message: "No updates available", + type: "info", + }) + } + }, + }, ]); appTray.setContextMenu(trayMenu); diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index 348d5bbd3f..b42c074641 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -1,14 +1,16 @@ import type { ClusterId } from "../common/cluster-store"; import { clusterStore } from "../common/cluster-store"; -import { BrowserWindow, dialog, ipcMain, shell, webContents } from "electron" +import { BrowserWindow, dialog, ipcMain, shell, Tray, webContents } from "electron" import windowStateKeeper from "electron-window-state" import { observable } from "mobx"; import { initMenu } from "./menu"; +import { setupTrayIcon } from "./tray"; export class WindowManager { - protected mainView: BrowserWindow; + public mainView: BrowserWindow; protected splashWindow: BrowserWindow; protected windowState: windowStateKeeper.State; + protected trayIcon: Tray; @observable activeClusterId: ClusterId; @@ -48,21 +50,38 @@ export class WindowManager { // load & show app this.showMain(); - initMenu(this); + this.initMenus(); } - navigate({ url, channel, frameId }: { url: string, channel: string, frameId?: number }) { + async initMenus() { + initMenu(this); + this.trayIcon = await setupTrayIcon(this); + } + + bringToTop() { + this.mainView.show(); + } + + sendToView({ channel, frameId, data = [] }: { channel: string, frameId?: number, data?: any[] }) { if (frameId) { - this.mainView.webContents.sendToFrame(frameId, channel, url); + this.mainView.webContents.sendToFrame(frameId, channel, ...data); } else { - this.mainView.webContents.send(channel, url); + this.mainView.webContents.send(channel, ...data); } } - reload({ channel }: { channel: string }) { + navigate(url: string, frameId?: number) { + this.sendToView({ + channel: "menu:navigate", + frameId: frameId, + data: [url], + }) + } + + reload() { const frameId = clusterStore.getById(this.activeClusterId)?.frameId; if (frameId) { - this.mainView.webContents.sendToFrame(frameId, channel); + this.sendToView({ channel: "menu:reload", frameId }); } else { webContents.getFocusedWebContents()?.reload(); } diff --git a/src/renderer/components/icon/logo.svg b/src/renderer/components/icon/logo-kontena.svg similarity index 100% rename from src/renderer/components/icon/logo.svg rename to src/renderer/components/icon/logo-kontena.svg diff --git a/src/renderer/components/icon/logo-full.svg b/src/renderer/components/icon/logo-lens.svg similarity index 100% rename from src/renderer/components/icon/logo-full.svg rename to src/renderer/components/icon/logo-lens.svg diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index a3a3271e0d..bde5036459 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -79,7 +79,7 @@ export class Sidebar extends React.Component {
- +
Lens