fix: menu bar and dock icon settings (#2490)

Co-authored-by: Zane Staggs <zane@squareup.com>
This commit is contained in:
Oliver
2025-05-29 14:29:21 -05:00
committed by GitHub
parent 3d5d3cedca
commit 4ff5ed462a
7 changed files with 228 additions and 6 deletions
+12 -4
View File
@@ -2,12 +2,20 @@
# Only auto-format desktop TS code if relevant files are modified
if git diff --cached --name-only | grep -q "^ui/desktop/"; then
. "$(dirname -- "$0")/_/husky.sh"
cd ui/desktop && npx lint-staged
if [ -d "ui/desktop" ]; then
. "$(dirname -- "$0")/_/husky.sh"
cd ui/desktop && npx lint-staged
else
echo "Warning: ui/desktop directory does not exist, skipping lint-staged"
fi
fi
# Only auto-format ui-v2 TS code if relevant files are modified
if git diff --cached --name-only | grep -q "^ui-v2/"; then
. "$(dirname -- "$0")/_/husky.sh"
cd ui-v2 && npx lint-staged
if [ -d "ui-v2" ]; then
. "$(dirname -- "$0")/_/husky.sh"
cd ui-v2 && npx lint-staged
else
echo "Warning: ui-v2 directory does not exist, skipping lint-staged"
fi
fi
@@ -7,6 +7,7 @@ import { ModeSection } from './mode/ModeSection';
import { ToolSelectionStrategySection } from './tool_selection_strategy/ToolSelectionStrategySection';
import SessionSharingSection from './sessions/SessionSharingSection';
import { ResponseStylesSection } from './response_styles/ResponseStylesSection';
import AppSettingsSection from './app/AppSettingsSection';
import { ExtensionConfig } from '../../api';
import MoreMenuLayout from '../more_menu/MoreMenuLayout';
@@ -53,6 +54,8 @@ export default function SettingsView({
<ResponseStylesSection />
{/* Tool Selection Strategy */}
<ToolSelectionStrategySection setView={setView} />
{/* App Settings */}
<AppSettingsSection />
</div>
</div>
</div>
@@ -0,0 +1,112 @@
import { useState, useEffect } from 'react';
import { Switch } from '../../ui/switch';
export default function AppSettingsSection() {
const [menuBarIconEnabled, setMenuBarIconEnabled] = useState(true);
const [dockIconEnabled, setDockIconEnabled] = useState(true);
const [isMacOS, setIsMacOS] = useState(false);
const [isDockSwitchDisabled, setIsDockSwitchDisabled] = useState(false);
// Check if running on macOS
useEffect(() => {
setIsMacOS(window.electron.platform === 'darwin');
}, []);
// Load menu bar and dock icon states
useEffect(() => {
window.electron.getMenuBarIconState().then((enabled) => {
setMenuBarIconEnabled(enabled);
});
if (isMacOS) {
window.electron.getDockIconState().then((enabled) => {
setDockIconEnabled(enabled);
});
}
}, [isMacOS]);
const handleMenuBarIconToggle = async () => {
const newState = !menuBarIconEnabled;
// If we're turning off the menu bar icon and the dock icon is hidden,
// we need to show the dock icon to maintain accessibility
if (!newState && !dockIconEnabled && isMacOS) {
const success = await window.electron.setDockIcon(true);
if (success) {
setDockIconEnabled(true);
}
}
const success = await window.electron.setMenuBarIcon(newState);
if (success) {
setMenuBarIconEnabled(newState);
}
};
const handleDockIconToggle = async () => {
const newState = !dockIconEnabled;
// If we're turning off the dock icon and the menu bar icon is hidden,
// we need to show the menu bar icon to maintain accessibility
if (!newState && !menuBarIconEnabled) {
const success = await window.electron.setMenuBarIcon(true);
if (success) {
setMenuBarIconEnabled(true);
}
}
// Disable the switch to prevent rapid toggling
setIsDockSwitchDisabled(true);
setTimeout(() => {
setIsDockSwitchDisabled(false);
}, 1000);
// Set the dock icon state
const success = await window.electron.setDockIcon(newState);
if (success) {
setDockIconEnabled(newState);
}
};
return (
<section id="appSettings" className="px-8">
<div className="flex justify-between items-center mb-2">
<h2 className="text-xl font-medium text-textStandard">App Settings</h2>
</div>
<div className="pb-8">
<p className="text-sm text-textStandard mb-6">Configure Goose app</p>
<div>
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-textStandard">Menu Bar Icon</h3>
<p className="text-xs text-textSubtle max-w-md mt-[2px]">
Show Goose in the menu bar
</p>
</div>
<div className="flex items-center">
<Switch
checked={menuBarIconEnabled}
onCheckedChange={handleMenuBarIconToggle}
variant="mono"
/>
</div>
</div>
{isMacOS && (
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-textStandard">Dock Icon</h3>
<p className="text-xs text-textSubtle max-w-md mt-[2px]">Show Goose in the dock</p>
</div>
<div className="flex items-center">
<Switch
disabled={isDockSwitchDisabled}
checked={dockIconEnabled}
onCheckedChange={handleDockIconToggle}
variant="mono"
/>
</div>
</div>
)}
</div>
</div>
</section>
);
}
@@ -29,7 +29,7 @@ export const ResponseStylesSection = () => {
<div className="flex justify-between items-center mb-2">
<h2 className="text-xl font-medium text-textStandard">Response Styles</h2>
</div>
<div className="pb-8">
<div className="border-b border-borderSubtle pb-8">
<p className="text-sm text-textStandard mb-6">
Choose how Goose should format and style its responses
</p>
+88 -1
View File
@@ -574,7 +574,17 @@ const createChat = async (
// Track tray instance
let tray: Tray | null = null;
const destroyTray = () => {
if (tray) {
tray.destroy();
tray = null;
}
};
const createTray = () => {
// If tray already exists, destroy it first
destroyTray();
const isDev = process.env.NODE_ENV === 'development';
let iconPath: string;
@@ -701,6 +711,73 @@ ipcMain.handle('directory-chooser', (_event, replace: boolean = false) => {
return openDirectoryDialog(replace);
});
// Handle menu bar icon visibility
ipcMain.handle('set-menu-bar-icon', async (_event, show: boolean) => {
try {
const settings = loadSettings();
settings.showMenuBarIcon = show;
saveSettings(settings);
if (show) {
createTray();
} else {
destroyTray();
}
return true;
} catch (error) {
console.error('Error setting menu bar icon:', error);
return false;
}
});
ipcMain.handle('get-menu-bar-icon-state', () => {
try {
const settings = loadSettings();
return settings.showMenuBarIcon ?? true;
} catch (error) {
console.error('Error getting menu bar icon state:', error);
return true;
}
});
// Handle dock icon visibility (macOS only)
ipcMain.handle('set-dock-icon', async (_event, show: boolean) => {
try {
if (process.platform !== 'darwin') return false;
const settings = loadSettings();
settings.showDockIcon = show;
saveSettings(settings);
if (show) {
await app.dock.show();
} else {
// Only hide the dock if we have a menu bar icon to maintain accessibility
if (settings.showMenuBarIcon) {
app.dock.hide();
setTimeout(() => {
focusWindow();
}, 50);
}
}
return true;
} catch (error) {
console.error('Error setting dock icon:', error);
return false;
}
});
ipcMain.handle('get-dock-icon-state', () => {
try {
if (process.platform !== 'darwin') return true;
const settings = loadSettings();
return settings.showDockIcon ?? true;
} catch (error) {
console.error('Error getting dock icon state:', error);
return true;
}
});
// Add file/directory selection handler
ipcMain.handle('select-file-or-directory', async () => {
const result = await dialog.showOpenDialog({
@@ -1122,10 +1199,20 @@ app.whenReady().then(async () => {
}, 5000);
}
// Create tray if enabled in settings
const settings = loadSettings();
if (settings.showMenuBarIcon) {
createTray();
}
// Handle dock icon visibility (macOS only)
if (process.platform === 'darwin' && !settings.showDockIcon && settings.showMenuBarIcon) {
app.dock.hide();
}
// Parse command line arguments
const { dirPath } = parseArgs();
createTray();
createNewWindow(app, dirPath);
// Get the existing menu
+8
View File
@@ -58,6 +58,10 @@ type ElectronAPI = {
writeFile: (directory: string, content: string) => Promise<boolean>;
getAllowedExtensions: () => Promise<string[]>;
getPathForFile: (file: File) => string;
setMenuBarIcon: (show: boolean) => Promise<boolean>;
getMenuBarIconState: () => Promise<boolean>;
setDockIcon: (show: boolean) => Promise<boolean>;
getDockIconState: () => Promise<boolean>;
on: (
channel: string,
callback: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void
@@ -117,6 +121,10 @@ const electronAPI: ElectronAPI = {
ipcRenderer.invoke('write-file', filePath, content),
getPathForFile: (file: File) => webUtils.getPathForFile(file),
getAllowedExtensions: () => ipcRenderer.invoke('get-allowed-extensions'),
setMenuBarIcon: (show: boolean) => ipcRenderer.invoke('set-menu-bar-icon', show),
getMenuBarIconState: () => ipcRenderer.invoke('get-menu-bar-icon-state'),
setDockIcon: (show: boolean) => ipcRenderer.invoke('set-dock-icon', show),
getDockIconState: () => ipcRenderer.invoke('get-dock-icon-state'),
on: (
channel: string,
callback: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void
+4
View File
@@ -10,6 +10,8 @@ export interface EnvToggles {
export interface Settings {
envToggles: EnvToggles;
showMenuBarIcon: boolean;
showDockIcon: boolean;
}
// Constants
@@ -20,6 +22,8 @@ const defaultSettings: Settings = {
GOOSE_SERVER__MEMORY: false,
GOOSE_SERVER__COMPUTER_CONTROLLER: false,
},
showMenuBarIcon: true,
showDockIcon: true,
};
// Settings management