Replace logo with platform-dependent button to open app menu (#239)

Replace the logo in the tab bar with an ellipsis icon that shows on
Linux and Windows and, when clicked, opens up the system menus. This
also fixes an issue I noticed where the context menus were set to the
wrong coordinates when a window was zoomed in.


![image](https://github.com/user-attachments/assets/1be77cad-73a6-4cb8-b545-08ec908f1d9a)

![image](https://github.com/user-attachments/assets/0beef938-15f8-4084-b7bd-7d9f995187ef)
This commit is contained in:
Evan Simkowitz 2024-08-16 16:18:42 -07:00 committed by GitHub
parent 18e2c7ed92
commit 03587184a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 60 additions and 66 deletions

View File

@ -587,12 +587,16 @@ async function createNewWaveWindow() {
electron.ipcMain.on("openNewWindow", () => fireAndForget(createNewWaveWindow)); electron.ipcMain.on("openNewWindow", () => fireAndForget(createNewWaveWindow));
electron.ipcMain.on("contextmenu-show", (event, menuDefArr: ElectronContextMenuItem[], { x, y }) => { electron.ipcMain.on("contextmenu-show", (event, menuDefArr?: ElectronContextMenuItem[]) => {
if (menuDefArr == null || menuDefArr.length == 0) { const window = electron.BrowserWindow.fromWebContents(event.sender);
if (menuDefArr?.length === 0) {
return; return;
} }
const menu = convertMenuDefArrToMenu(menuDefArr); const menu = menuDefArr ? convertMenuDefArrToMenu(menuDefArr) : getAppMenu();
menu.popup({ x, y }); const { x, y } = electron.screen.getCursorScreenPoint();
const windowPos = window.getPosition();
menu.popup({ window, x: x - windowPos[0], y: y - windowPos[1] });
event.returnValue = true; event.returnValue = true;
}); });
@ -640,7 +644,7 @@ function convertMenuDefArrToMenu(menuDefArr: ElectronContextMenuItem[]): electro
return electron.Menu.buildFromTemplate(menuItems); return electron.Menu.buildFromTemplate(menuItems);
} }
function makeAppMenu() { function getAppMenu() {
const fileMenu: Electron.MenuItemConstructorOptions[] = [ const fileMenu: Electron.MenuItemConstructorOptions[] = [
{ {
label: "New Window", label: "New Window",
@ -668,19 +672,15 @@ function makeAppMenu() {
{ {
type: "separator", type: "separator",
}, },
];
if (unamePlatform === "darwin") {
appMenu.push(
{ {
role: "services", role: "services",
}, },
{ {
type: "separator", type: "separator",
}, },
{
label: "Toggle Menu Bar Visibility",
visible: unamePlatform != "darwin",
click: (_, window) => {
window.autoHideMenuBar = !window.autoHideMenuBar;
},
},
{ {
role: "hide", role: "hide",
}, },
@ -689,11 +689,12 @@ function makeAppMenu() {
}, },
{ {
type: "separator", type: "separator",
}, }
{ );
}
appMenu.push({
role: "quit", role: "quit",
}, });
];
const viewMenu: Electron.MenuItemConstructorOptions[] = [ const viewMenu: Electron.MenuItemConstructorOptions[] = [
{ {
role: "forceReload", role: "forceReload",
@ -776,8 +777,11 @@ function makeAppMenu() {
submenu: windowMenu, submenu: windowMenu,
}, },
]; ];
const menu = electron.Menu.buildFromTemplate(menuTemplate); return electron.Menu.buildFromTemplate(menuTemplate);
electron.Menu.setApplicationMenu(menu); }
function makeAppMenu() {
electron.Menu.setApplicationMenu(getAppMenu());
} }
electronApp.on("window-all-closed", () => { electronApp.on("window-all-closed", () => {

View File

@ -28,32 +28,21 @@
height: 33px; height: 33px;
} }
.dev-label { .dev-label,
.app-menu-button {
height: 100%; height: 100%;
font-size: 26px; font-size: 26px;
user-select: none; user-select: none;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-top: 2px; margin: 2px 2px 0 0;
}
.dev-label {
color: var(--accent-color); color: var(--accent-color);
} }
.prod-label {
font-size: 26px;
width: 1.25em;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
img {
height: 26px;
width: 26px;
margin-top: 4px;
}
}
.add-tab-btn { .add-tab-btn {
width: 22px; width: 22px;
height: 100%; height: 100%;

View File

@ -3,13 +3,12 @@
import { WindowDrag } from "@/element/windowdrag"; import { WindowDrag } from "@/element/windowdrag";
import { deleteLayoutModelForTab } from "@/layout/index"; import { deleteLayoutModelForTab } from "@/layout/index";
import { atoms, getApi, isDev } from "@/store/global"; import { atoms, getApi, isDev, PLATFORM } from "@/store/global";
import * as services from "@/store/services"; import * as services from "@/store/services";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { OverlayScrollbars } from "overlayscrollbars"; import { OverlayScrollbars } from "overlayscrollbars";
import React, { createRef, useCallback, useEffect, useRef, useState } from "react"; import React, { createRef, useCallback, useEffect, useRef, useState } from "react";
import { debounce } from "throttle-debounce"; import { debounce } from "throttle-debounce";
import logoPng from "../../../public/logos/wave-logo.png";
import { Button } from "../element/button"; import { Button } from "../element/button";
import { Tab } from "./tab"; import { Tab } from "./tab";
import "./tabbar.less"; import "./tabbar.less";
@ -482,21 +481,22 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => {
return tabIds.indexOf(tabId) === tabIds.indexOf(activetabid) - 1; return tabIds.indexOf(tabId) === tabIds.indexOf(activetabid) - 1;
}; };
function onEllipsisClick() {
getApi().showContextMenu();
}
const tabsWrapperWidth = tabIds.length * tabWidthRef.current; const tabsWrapperWidth = tabIds.length * tabWidthRef.current;
let waveLabel: React.ReactNode = null; const devLabel = isDev() ? (
if (isDev()) {
waveLabel = (
<div className="dev-label"> <div className="dev-label">
<i className="fa fa-brands fa-dev fa-fw" /> <i className="fa fa-brands fa-dev fa-fw" />
</div> </div>
); ) : undefined;
} else { const appMenuButton =
waveLabel = ( PLATFORM !== "darwin" ? (
<div className="prod-label"> <div className="app-menu-button" onClick={onEllipsisClick}>
<img src={logoPng} /> <i className="fa fa-ellipsis" />
</div> </div>
); ) : undefined;
}
function onUpdateAvailableClick() { function onUpdateAvailableClick() {
getApi().installAppUpdate(); getApi().installAppUpdate();
@ -519,7 +519,8 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => {
return ( return (
<div ref={tabbarWrapperRef} className="tab-bar-wrapper"> <div ref={tabbarWrapperRef} className="tab-bar-wrapper">
<WindowDrag ref={draggerLeftRef} className="left" /> <WindowDrag ref={draggerLeftRef} className="left" />
{waveLabel} {appMenuButton}
{devLabel}
<div className="tab-bar" ref={tabBarRef} data-overlayscrollbars-initialize> <div className="tab-bar" ref={tabBarRef} data-overlayscrollbars-initialize>
<div className="tabs-wrapper" ref={tabsWrapperRef} style={{ width: `${tabsWrapperWidth}px` }}> <div className="tabs-wrapper" ref={tabsWrapperRef} style={{ width: `${tabsWrapperWidth}px` }}>
{tabIds.map((tabId, index) => { {tabIds.map((tabId, index) => {

View File

@ -58,7 +58,7 @@ declare global {
getPlatform: () => NodeJS.Platform; getPlatform: () => NodeJS.Platform;
getEnv: (varName: string) => string; getEnv: (varName: string) => string;
showContextMenu: (menu: ElectronContextMenuItem[], position: { x: number; y: number }) => void; showContextMenu: (menu?: ElectronContextMenuItem[]) => void;
onContextMenuClick: (callback: (id: string) => void) => void; onContextMenuClick: (callback: (id: string) => void) => void;
onNavigate: (callback: (url: string) => void) => void; onNavigate: (callback: (url: string) => void) => void;
onIframeNavigate: (callback: (url: string) => void) => void; onIframeNavigate: (callback: (url: string) => void) => void;