From a4bcfc1e524c60c04cf7fbac23a5c56ec8a5c564 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 22 Jul 2020 17:10:37 +0300 Subject: [PATCH] menu.ts: refactoring and fixes, reuse route paths from views (renderer) Signed-off-by: Roman --- src/common/ipc.ts | 22 +- src/main/index.ts | 4 +- src/main/menu.ts | 203 +++++++----------- .../components/+whats-new/whats-new.tsx | 2 +- src/renderer/components/drawer/drawer.tsx | 8 +- src/renderer/index.tsx | 4 +- src/renderer/navigation.ts | 14 +- 7 files changed, 118 insertions(+), 139 deletions(-) diff --git a/src/common/ipc.ts b/src/common/ipc.ts index 96a0157358..db65b00e6a 100644 --- a/src/common/ipc.ts +++ b/src/common/ipc.ts @@ -69,16 +69,24 @@ export function handleIpc(channel: IpcChannel, handler: IpcMessageHandler, optio export interface IpcPairOptions { channel: IpcChannel handle?: IpcMessageHandler - options?: IpcHandleOpts + autoBind?: boolean; + timeout?: number; } -export function createIpcChannel({ channel, ...initOpts }: IpcPairOptions) { +// todo: improve api +export function createIpcChannel({ channel, autoBind, ...initOpts }: IpcPairOptions) { + const bindHandler = (opts: { handler?: IpcMessageHandler, options?: IpcHandleOpts } = {}) => { + const handler = opts.handler || initOpts.handle || Function; + const options = opts.options || { timeout: initOpts.timeout }; + handleIpc(channel, handler, options); + }; + if (autoBind) { + bindHandler(); + } return { - handleInMain: (opts: Partial> = {}) => { - const { handle = initOpts.handle, options = initOpts.options } = opts; - return handleIpc(channel, handle, options); - }, - invokeFromRenderer: (...args: any[]) => { + channel: channel, + handleInMain: bindHandler, + invokeFromRenderer(...args: any[]) { return invokeIpc(channel, ...args); }, } diff --git a/src/main/index.ts b/src/main/index.ts index fc5dcade85..a897b1ac9e 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -5,7 +5,7 @@ import "../common/prometheus-providers" import { app, dialog } from "electron" import { appName, appProto, staticDir, staticProto } from "../common/vars"; import path from "path" -import initMenu from "./menu" +import { initMenu } from "./menu" import { LensProxy } from "./lens-proxy" import { WindowManager } from "./window-manager"; import { ClusterManager } from "./cluster-manager"; @@ -41,7 +41,6 @@ async function main() { const updater = new AppUpdater() updater.start(); - initMenu(); registerFileProtocol(appProto, app.getPath("userData")); registerFileProtocol(staticProto, staticDir); @@ -76,6 +75,7 @@ async function main() { // create window manager and open app windowManager = new WindowManager(proxyPort); windowManager.showSplash(); + initMenu(windowManager); } app.on("ready", main); diff --git a/src/main/menu.ts b/src/main/menu.ts index f35abf5362..0bab5ed617 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -1,113 +1,71 @@ +import type { WindowManager } from "./window-manager"; import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, shell, webContents } from "electron" +import { broadcastIpc } from "../common/ipc"; import { appName, isMac, issuesTrackerUrl, isWindows, slackUrl } from "../common/vars"; +import { clusterStore } from "../common/cluster-store"; +import { addClusterURL } from "../renderer/components/+add-cluster/add-cluster.route"; +import { preferencesURL } from "../renderer/components/+preferences/preferences.route"; +import { whatsNewURL } from "../renderer/components/+whats-new/whats-new.route"; +import { clusterSettingsURL } from "../renderer/components/+cluster-settings/cluster-settings.route"; -// todo: refactor + split menu sections to separated files, e.g. menus/file.menu.ts +export function initMenu(windowManager: WindowManager) { + const menuItems: MenuItemConstructorOptions[] = []; -export interface MenuOptions { - logoutHook: any; - addClusterHook: any; - clusterSettingsHook: any; - showWhatsNewHook: any; - showPreferencesHook: any; - // all the above are really () => void type functions -} + function navigate(url: string) { + const activeClusterId = clusterStore.activeClusterId; + const view = windowManager.getView(activeClusterId); + if (view) { + broadcastIpc({ + channel: "menu:navigate", + webContentId: view.id, + args: [url], + }); + } + } -function setClusterSettingsEnabled(enabled: boolean) { - const menuIndex = isMac ? 1 : 0 - Menu.getApplicationMenu().items[menuIndex].submenu.items[1].enabled = enabled -} + function macOnly(menuItems: MenuItemConstructorOptions[]): MenuItemConstructorOptions[] { + if (!isMac) return []; + return menuItems; + } -export function showAbout(_menuitem: MenuItem, browserWindow: BrowserWindow) { - const appInfo = [ - `${appName}: ${app.getVersion()}`, - `Electron: ${process.versions.electron}`, - `Chrome: ${process.versions.chrome}`, - `Copyright 2020 Lakend Labs, Inc.`, - ] - dialog.showMessageBoxSync(browserWindow, { - title: `${isWindows ? " ".repeat(2) : ""}${appName}`, - type: "info", - buttons: ["Close"], - message: `Lens`, - detail: appInfo.join("\r\n") - }) -} - -/** - * Constructs the menu based on the example at: https://electronjs.org/docs/api/menu#main-process - * Menu items are constructed piece-by-piece to have slightly better control on individual sub-menus - */ -export default function initMenu(opts: Partial = {}) { - const mt: MenuItemConstructorOptions[] = []; - const macAppMenu: MenuItemConstructorOptions = { - label: app.getName(), + // "File" submenu + menuItems.push({ + label: isMac ? app.getName() : "File", submenu: [ { - label: "About Lens", - click: showAbout + label: 'Add Cluster', + click() { + navigate(addClusterURL()) + } + }, + { + label: 'Cluster Settings', + click() { + navigate(clusterSettingsURL()) + } }, { type: 'separator' }, { label: 'Preferences', - click: opts.showPreferencesHook, - enabled: true + click() { + navigate(preferencesURL()) + } }, - { type: 'separator' }, - { role: 'services' }, - { type: 'separator' }, - { role: 'hide' }, - { role: 'hideOthers' }, - { role: 'unhide' }, + ...macOnly([ + { type: 'separator' }, + { role: 'services' }, + { type: 'separator' }, + { role: 'hide' }, + { role: 'hideOthers' }, + { role: 'unhide' }, + ]), { type: 'separator' }, { role: 'quit' } ] - }; - if (isMac) { - mt.push(macAppMenu); - } + }); - let fileMenu: MenuItemConstructorOptions; - if (isMac) { - fileMenu = { - label: 'File', - submenu: [{ - label: 'Add Cluster...', - click: opts.addClusterHook, - }, - { - label: 'Cluster Settings', - click: opts.clusterSettingsHook, - enabled: false - } - ] - } - } else { - fileMenu = { - label: 'File', - submenu: [ - { - label: 'Add Cluster...', - click: opts.addClusterHook, - }, - { - label: 'Cluster Settings', - click: opts.clusterSettingsHook, - enabled: false - }, - { type: 'separator' }, - { - label: 'Preferences', - click: opts.showPreferencesHook, - enabled: true - }, - { type: 'separator' }, - { role: 'quit' } - ] - } - } - mt.push(fileMenu); - - const editMenu: MenuItemConstructorOptions = { + // "Edit" submenu + menuItems.push({ label: 'Edit', submenu: [ { role: 'undo' }, @@ -120,10 +78,10 @@ export default function initMenu(opts: Partial = {}) { { type: 'separator' }, { role: 'selectAll' }, ] - }; - mt.push(editMenu); + }); - const viewMenu: MenuItemConstructorOptions = { + // "View" submenu + menuItems.push({ label: 'View', submenu: [ { @@ -155,20 +113,26 @@ export default function initMenu(opts: Partial = {}) { { type: 'separator' }, { role: 'togglefullscreen' } ] - }; - mt.push(viewMenu); + }) - const helpMenu: MenuItemConstructorOptions = { + // "Help" submenu + menuItems.push({ role: 'help', submenu: [ { - label: 'License', + label: "What's new?", + click() { + navigate(whatsNewURL()) + }, + }, + { + label: "License", click: async () => { shell.openExternal('https://lakendlabs.com/licenses/lens-eula.md'); }, }, { - label: 'Community Slack', + label: "Community Slack", click: async () => { shell.openExternal(slackUrl); }, @@ -180,27 +144,26 @@ export default function initMenu(opts: Partial = {}) { }, }, { - label: "What's new?", - click: opts.showWhatsNewHook, - }, - ...(!isMac ? [{ label: "About Lens", - click: showAbout - } as MenuItemConstructorOptions] : []) + click(menuItem: MenuItem, browserWindow: BrowserWindow) { + const appInfo = [ + `${appName}: ${app.getVersion()}`, + `Electron: ${process.versions.electron}`, + `Chrome: ${process.versions.chrome}`, + `Copyright 2020 Lakend Labs, Inc.`, + ] + dialog.showMessageBoxSync(browserWindow, { + title: `${isWindows ? " ".repeat(2) : ""}${appName}`, + type: "info", + buttons: ["Close"], + message: `Lens`, + detail: appInfo.join("\r\n") + }) + } + } ] - }; - mt.push(helpMenu); + }); - const menu = Menu.buildFromTemplate(mt); + const menu = Menu.buildFromTemplate(menuItems); Menu.setApplicationMenu(menu); - - // const promiseIpc = new PromiseIpc({ timeout: 2000 }) - // - // promiseIpc.on("enableClusterSettingsMenuItem", (clusterId: string) => { - // setClusterSettingsEnabled(true) - // }); - // - // promiseIpc.on("disableClusterSettingsMenuItem", () => { - // setClusterSettingsEnabled(false) - // }); } diff --git a/src/renderer/components/+whats-new/whats-new.tsx b/src/renderer/components/+whats-new/whats-new.tsx index 1fcd0691d9..9bde7da141 100644 --- a/src/renderer/components/+whats-new/whats-new.tsx +++ b/src/renderer/components/+whats-new/whats-new.tsx @@ -33,7 +33,7 @@ export class WhatsNew extends React.Component {