diff --git a/desktop/src/main.ts b/desktop/src/main.ts index dc27c7435..4383fa73f 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -8,7 +8,7 @@ * * https://www.electronjs.org/docs/latest/tutorial/process-model#the-main-process */ -import { app, BrowserWindow } from "electron/main"; +import { app, BrowserWindow, Menu } from "electron/main"; import serveNextAt from "next-electron-server"; import { existsSync } from "node:fs"; import fs from "node:fs/promises"; @@ -21,11 +21,11 @@ import { handleExternalLinks, logStartupBanner, setupMacWindowOnDockIconClick, - setupMainMenu, setupTrayItem, } from "./main/init"; import { attachFSWatchIPCHandlers, attachIPCHandlers } from "./main/ipc"; import log, { initLogging } from "./main/log"; +import { createApplicationMenu } from "./main/menu"; import { isDev } from "./main/util"; import { setupAutoUpdater } from "./services/appUpdater"; import { initWatcher } from "./services/chokidar"; @@ -168,7 +168,7 @@ const main = () => { const watcher = initWatcher(mainWindow); setupTrayItem(mainWindow); setupMacWindowOnDockIconClick(); - setupMainMenu(mainWindow); + Menu.setApplicationMenu(await createApplicationMenu(mainWindow)); attachIPCHandlers(); attachFSWatchIPCHandlers(watcher); if (!isDev) setupAutoUpdater(mainWindow); diff --git a/desktop/src/main/init.ts b/desktop/src/main/init.ts index 270609bf7..6e463e4c4 100644 --- a/desktop/src/main/init.ts +++ b/desktop/src/main/init.ts @@ -1,14 +1,13 @@ -import { app, BrowserWindow, Menu, nativeImage, Tray } from "electron"; +import { app, BrowserWindow, nativeImage, Tray } from "electron"; import { existsSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import { isAppQuitting, rendererURL } from "../main"; -import { setupAutoUpdater } from "../services/appUpdater"; import autoLauncher from "../services/autoLauncher"; import { getHideDockIconPreference } from "../services/userPreference"; import { isPlatform } from "../utils/common/platform"; -import { buildContextMenu, buildMenuBar } from "../utils/menu"; import log from "./log"; +import { createTrayContextMenu } from "./menu"; import { isDev } from "./util"; /** @@ -78,8 +77,7 @@ export const createWindow = async () => { return mainWindow; }; -export async function handleUpdates(mainWindow: BrowserWindow) { -} +export async function handleUpdates(mainWindow: BrowserWindow) {} export const setupTrayItem = (mainWindow: BrowserWindow) => { const iconName = isPlatform("mac") @@ -92,7 +90,7 @@ export const setupTrayItem = (mainWindow: BrowserWindow) => { const trayIcon = nativeImage.createFromPath(trayImgPath); const tray = new Tray(trayIcon); tray.setToolTip("ente"); - tray.setContextMenu(buildContextMenu(mainWindow)); + tray.setContextMenu(createTrayContextMenu(mainWindow)); }; export function handleDownloads(mainWindow: BrowserWindow) { @@ -142,10 +140,6 @@ export function setupMacWindowOnDockIconClick() { }); } -export async function setupMainMenu(mainWindow: BrowserWindow) { - Menu.setApplicationMenu(await buildMenuBar(mainWindow)); -} - export async function handleDockIconHideOnAutoLaunch() { const shouldHideDockIcon = getHideDockIconPreference(); const wasAutoLaunched = await autoLauncher.wasAutoLaunched(); diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts new file mode 100644 index 000000000..ddaacf32d --- /dev/null +++ b/desktop/src/main/menu.ts @@ -0,0 +1,222 @@ +import { + app, + BrowserWindow, + Menu, + MenuItemConstructorOptions, + shell, +} from "electron"; +import { setIsAppQuitting } from "../main"; +import { forceCheckForUpdateAndNotify } from "../services/appUpdater"; +import autoLauncher from "../services/autoLauncher"; +import { + getHideDockIconPreference, + setHideDockIconPreference, +} from "../services/userPreference"; +import { openLogDirectory } from "./util"; + +/** Create and return the entries in the app's main menu bar */ +export const createApplicationMenu = async (mainWindow: BrowserWindow) => { + const isMac = process.platform == "darwin"; + + // The state of checkboxes + // + // Whenever the menu is redrawn the current value of these variables is used + // to set the checked state for the various settings checkboxes. + let isAutoLaunchEnabled = await autoLauncher.isEnabled(); + let shouldHideDockIcon = getHideDockIconPreference(); + + const handleCheckForUpdates = () => + forceCheckForUpdateAndNotify(mainWindow); + + const handleViewChangelog = () => + shell.openExternal( + "https://github.com/ente-io/ente/blob/main/desktop/CHANGELOG.md", + ); + + const toggleAutoLaunch = () => { + autoLauncher.toggleAutoLaunch(); + isAutoLaunchEnabled = !isAutoLaunchEnabled; + }; + + const toggleHideDockIcon = () => { + setHideDockIconPreference(!shouldHideDockIcon); + shouldHideDockIcon = !shouldHideDockIcon; + }; + + const handleHelp = () => shell.openExternal("https://help.ente.io/photos/"); + + const handleSupport = () => shell.openExternal("mailto:support@ente.io"); + + const handleBlog = () => shell.openExternal("https://ente.io/blog/"); + + const handleViewLogs = openLogDirectory; + + return Menu.buildFromTemplate([ + { + label: "ente", + submenu: [ + ...((isMac + ? [ + { + label: "About Ente", + role: "about", + }, + ] + : []) as MenuItemConstructorOptions[]), + { type: "separator" }, + { + label: "Check for Updates...", + click: handleCheckForUpdates, + }, + { + label: "View Changelog", + click: handleViewChangelog, + }, + { type: "separator" }, + + { + label: "Preferences", + submenu: [ + { + label: "Open Ente on Startup", + type: "checkbox", + checked: isAutoLaunchEnabled, + click: toggleAutoLaunch, + }, + { + label: "Hide Dock Icon", + type: "checkbox", + checked: shouldHideDockIcon, + click: toggleHideDockIcon, + }, + ], + }, + + { type: "separator" }, + ...((isMac + ? [ + { + label: "Hide Ente", + role: "hide", + }, + { + label: "Hide Others", + role: "hideOthers", + }, + ] + : []) as MenuItemConstructorOptions[]), + + { type: "separator" }, + { + label: "Quit", + role: "quit", + }, + ], + }, + { + label: "Edit", + submenu: [ + { label: "Undo", role: "undo" }, + { label: "Redo", role: "redo" }, + { type: "separator" }, + { label: "Cut", role: "cut" }, + { label: "Copy", role: "copy" }, + { label: "Paste", role: "paste" }, + { label: "Select All", role: "selectAll" }, + ...((isMac + ? [ + { type: "separator" }, + { + label: "Speech", + submenu: [ + { + role: "startSpeaking", + label: "start speaking", + }, + { + role: "stopSpeaking", + label: "stop speaking", + }, + ], + }, + ] + : []) as MenuItemConstructorOptions[]), + ], + }, + { + label: "View", + submenu: [ + { label: "Reload", role: "reload" }, + { label: "Toggle Dev Tools", role: "toggleDevTools" }, + { type: "separator" }, + { label: "Toggle Full Screen", role: "togglefullscreen" }, + ], + }, + { + label: "Window", + submenu: [ + { label: "Minimize", role: "minimize" }, + { label: "Zoom", role: "zoom" }, + { label: "Close", role: "close" }, + ...((isMac + ? [ + { type: "separator" }, + { label: "Bring All to Front", role: "front" }, + { type: "separator" }, + { label: "Ente", role: "window" }, + ] + : []) as MenuItemConstructorOptions[]), + ], + }, + { + label: "Help", + submenu: [ + { + label: "Ente Help", + click: handleHelp, + }, + { type: "separator" }, + { + label: "Support", + click: handleSupport, + }, + { + label: "Product Updates", + click: handleBlog, + }, + { type: "separator" }, + { + label: "View Logs", + click: handleViewLogs, + }, + ], + }, + ]); +}; + +/** + * Create and return a {@link Menu} that is shown when the user clicks on our + * system tray icon (e.g. the icon list at the top right of the screen on macOS) + */ +export const createTrayContextMenu = (mainWindow: BrowserWindow) => { + const handleOpen = () => { + mainWindow.maximize(); + mainWindow.show(); + }; + + const handleClose = () => { + setIsAppQuitting(true); + app.quit(); + }; + + return Menu.buildFromTemplate([ + { + label: "Open Ente", + click: handleOpen, + }, + { + label: "Quit Ente", + click: handleClose, + }, + ]); +}; diff --git a/desktop/src/utils/menu.ts b/desktop/src/utils/menu.ts deleted file mode 100644 index 8f7315da7..000000000 --- a/desktop/src/utils/menu.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { - app, - BrowserWindow, - Menu, - MenuItemConstructorOptions, - shell, -} from "electron"; -import ElectronLog from "electron-log"; -import { setIsAppQuitting } from "../main"; -import { openDirectory, openLogDirectory } from "../main/util"; -import { forceCheckForUpdateAndNotify } from "../services/appUpdater"; -import autoLauncher from "../services/autoLauncher"; -import { - getHideDockIconPreference, - setHideDockIconPreference, -} from "../services/userPreference"; -import { isPlatform } from "./common/platform"; - -export function buildContextMenu(mainWindow: BrowserWindow): Menu { - // eslint-disable-next-line camelcase - const contextMenu = Menu.buildFromTemplate([ - { - label: "Open ente", - click: function () { - mainWindow.maximize(); - mainWindow.show(); - }, - }, - { - label: "Quit ente", - click: function () { - ElectronLog.log("user quit the app"); - setIsAppQuitting(true); - app.quit(); - }, - }, - ]); - return contextMenu; -} - -export async function buildMenuBar(mainWindow: BrowserWindow): Promise { - let isAutoLaunchEnabled = await autoLauncher.isEnabled(); - const isMac = isPlatform("mac"); - let shouldHideDockIcon = getHideDockIconPreference(); - const template: MenuItemConstructorOptions[] = [ - { - label: "ente", - submenu: [ - ...((isMac - ? [ - { - label: "About ente", - role: "about", - }, - ] - : []) as MenuItemConstructorOptions[]), - { type: "separator" }, - { - label: "Check for updates...", - click: () => { - forceCheckForUpdateAndNotify(mainWindow); - }, - }, - { - label: "View Changelog", - click: () => { - shell.openExternal( - "https://github.com/ente-io/ente/blob/main/desktop/CHANGELOG.md", - ); - }, - }, - { type: "separator" }, - - { - label: "Preferences", - submenu: [ - { - label: "Open ente on startup", - type: "checkbox", - checked: isAutoLaunchEnabled, - click: () => { - autoLauncher.toggleAutoLaunch(); - isAutoLaunchEnabled = !isAutoLaunchEnabled; - }, - }, - { - label: "Hide dock icon", - type: "checkbox", - checked: shouldHideDockIcon, - click: () => { - setHideDockIconPreference(!shouldHideDockIcon); - shouldHideDockIcon = !shouldHideDockIcon; - }, - }, - ], - }, - - { type: "separator" }, - ...((isMac - ? [ - { - label: "Hide ente", - role: "hide", - }, - { - label: "Hide others", - role: "hideOthers", - }, - ] - : []) as MenuItemConstructorOptions[]), - - { type: "separator" }, - { - label: "Quit ente", - role: "quit", - }, - ], - }, - { - label: "Edit", - submenu: [ - { role: "undo", label: "Undo" }, - { role: "redo", label: "Redo" }, - { type: "separator" }, - { role: "cut", label: "Cut" }, - { role: "copy", label: "Copy" }, - { role: "paste", label: "Paste" }, - ...((isMac - ? [ - { - role: "pasteAndMatchStyle", - label: "Paste and match style", - }, - { role: "delete", label: "Delete" }, - { role: "selectAll", label: "Select all" }, - { type: "separator" }, - { - label: "Speech", - submenu: [ - { - role: "startSpeaking", - label: "start speaking", - }, - { - role: "stopSpeaking", - label: "stop speaking", - }, - ], - }, - ] - : [ - { type: "separator" }, - { role: "selectAll", label: "Select all" }, - ]) as MenuItemConstructorOptions[]), - ], - }, - { - label: "View", - submenu: [ - { role: "reload", label: "Reload" }, - { role: "forceReload", label: "Force reload" }, - { role: "toggleDevTools", label: "Toggle dev tools" }, - { type: "separator" }, - { role: "resetZoom", label: "Reset zoom" }, - { role: "zoomIn", label: "Zoom in" }, - { role: "zoomOut", label: "Zoom out" }, - { type: "separator" }, - { role: "togglefullscreen", label: "Toggle fullscreen" }, - ], - }, - { - label: "Window", - submenu: [ - { role: "close", label: "Close" }, - { role: "minimize", label: "Minimize" }, - ...((isMac - ? [ - { type: "separator" }, - { role: "front", label: "Bring to front" }, - { type: "separator" }, - { role: "window", label: "ente" }, - ] - : []) as MenuItemConstructorOptions[]), - ], - }, - { - label: "Help", - submenu: [ - { - label: "Ente Help", - click: () => - shell.openExternal("https://help.ente.io/photos/"), - }, - { type: "separator" }, - { - label: "Support", - click: () => shell.openExternal("mailto:support@ente.io"), - }, - { - label: "Product updates", - click: () => shell.openExternal("https://ente.io/blog/"), - }, - { type: "separator" }, - { - label: "View crash reports", - click: () => openDirectory(app.getPath("crashDumps")), - }, - { - label: "View logs", - click: openLogDirectory, - }, - ], - }, - ]; - return Menu.buildFromTemplate(template); -}