💄 style: add windows control and tray (#7665)

* add windows support

* add windows close support

* improve

* fix

* FIX

* improve builder

* improve windows icon
This commit is contained in:
Arvin Xu
2025-05-01 16:42:26 +08:00
committed by GitHub
parent 6d8ca6ab76
commit c5f3d13c14
24 changed files with 746 additions and 19 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -79,10 +79,10 @@ const config = {
},
npmRebuild: true,
nsis: {
allowToChangeInstallationDirectory: true,
artifactName: '${productName}-${version}-setup.${ext}',
createDesktopShortcut: 'always',
// allowToChangeInstallationDirectory: true,
// oneClick: false,
oneClick: false,
shortcutName: '${productName}',
uninstallDisplayName: '${productName}',
},

View File

@@ -23,7 +23,6 @@
@media (prefers-color-scheme: dark) {
body {
color: #f5f5f5;
background-color: #121212;
}
.error-message {
color: #f5f5f5;

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -2,6 +2,7 @@ import { InterceptRouteParams } from '@lobechat/electron-client-ipc';
import { extractSubPath, findMatchingRoute } from '~common/routes';
import { AppBrowsersIdentifiers, BrowsersIdentifiers } from '@/appBrowsers';
import { IpcClientEventSender } from '@/types/ipcClientEvent';
import { ControllerModule, ipcClientEvent, shortcut } from './index';
@@ -26,6 +27,21 @@ export default class BrowserWindowsCtr extends ControllerModule {
}
}
@ipcClientEvent('closeWindow')
closeWindow(data: undefined, sender: IpcClientEventSender) {
this.app.browserManager.closeWindow(sender.identifier);
}
@ipcClientEvent('minimizeWindow')
minimizeWindow(data: undefined, sender: IpcClientEventSender) {
this.app.browserManager.minimizeWindow(sender.identifier);
}
@ipcClientEvent('maximizeWindow')
maximizeWindow(data: undefined, sender: IpcClientEventSender) {
this.app.browserManager.maximizeWindow(sender.identifier);
}
/**
* Handle route interception requests
* Responsible for handling route interception requests from the renderer process

View File

@@ -0,0 +1,109 @@
import {
ShowTrayNotificationParams,
UpdateTrayIconParams,
UpdateTrayTooltipParams,
} from '@lobechat/electron-client-ipc';
import { createLogger } from '@/utils/logger';
import { ControllerModule, ipcClientEvent, shortcut } from './index';
// 创建日志记录器
const logger = createLogger('controllers:TrayMenuCtr');
export default class TrayMenuCtr extends ControllerModule {
/**
* 使用快捷键切换窗口可见性
*/
@shortcut('toggleMainWindow')
async toggleMainWindow() {
logger.debug('通过快捷键切换主窗口可见性');
const mainWindow = this.app.browserManager.getMainWindow();
mainWindow.toggleVisible();
}
/**
* 显示托盘气泡通知
* @param options 气泡选项
* @returns 操作结果
*/
@ipcClientEvent('showTrayNotification')
async showNotification(options: ShowTrayNotificationParams) {
logger.debug('显示托盘气泡通知');
if (process.platform === 'win32') {
const mainTray = this.app.trayManager.getMainTray();
if (mainTray) {
mainTray.displayBalloon({
content: options.content,
iconType: options.iconType || 'info',
title: options.title,
});
return { success: true };
}
}
return {
error: '托盘通知仅在 Windows 平台支持',
success: false
};
}
/**
* 更新托盘图标
* @param options 图标选项
* @returns 操作结果
*/
@ipcClientEvent('updateTrayIcon')
async updateTrayIcon(options: UpdateTrayIconParams) {
logger.debug('更新托盘图标');
if (process.platform === 'win32') {
const mainTray = this.app.trayManager.getMainTray();
if (mainTray && options.iconPath) {
try {
mainTray.updateIcon(options.iconPath);
return { success: true };
} catch (error) {
logger.error('更新托盘图标失败:', error);
return {
error: String(error),
success: false
};
}
}
}
return {
error: '托盘功能仅在 Windows 平台支持',
success: false
};
}
/**
* 更新托盘提示文本
* @param options 提示文本选项
* @returns 操作结果
*/
@ipcClientEvent('updateTrayTooltip')
async updateTrayTooltip(options: UpdateTrayTooltipParams) {
logger.debug('更新托盘提示文本');
if (process.platform === 'win32') {
const mainTray = this.app.trayManager.getMainTray();
if (mainTray && options.tooltip) {
mainTray.updateTooltip(options.tooltip);
return { success: true };
}
}
return {
error: '托盘功能仅在 Windows 平台支持',
success: false
};
}
}

View File

@@ -9,6 +9,7 @@ import { buildDir, nextStandaloneDir } from '@/const/dir';
import { isDev } from '@/const/env';
import { IControlModule } from '@/controllers';
import { IServiceModule } from '@/services';
import { IpcClientEventSender } from '@/types/ipcClientEvent';
import { createLogger } from '@/utils/logger';
import { CustomRequestHandler, createHandler } from '@/utils/next-electron-rsc';
@@ -18,6 +19,7 @@ import { IoCContainer } from './IoCContainer';
import MenuManager from './MenuManager';
import { ShortcutManager } from './ShortcutManager';
import { StoreManager } from './StoreManager';
import TrayManager from './TrayManager';
import { UpdaterManager } from './UpdaterManager';
const logger = createLogger('core:App');
@@ -38,6 +40,7 @@ export class App {
storeManager: StoreManager;
updaterManager: UpdaterManager;
shortcutManager: ShortcutManager;
trayManager: TrayManager;
/**
* whether app is in quiting
@@ -92,6 +95,7 @@ export class App {
this.menuManager = new MenuManager(this);
this.updaterManager = new UpdaterManager(this);
this.shortcutManager = new ShortcutManager(this);
this.trayManager = new TrayManager(this);
// register the schema to interceptor url
// it should register before app ready
@@ -130,6 +134,11 @@ export class App {
this.browserManager.initializeBrowsers();
// Initialize tray manager
if (process.platform === 'win32') {
this.trayManager.initializeTrays();
}
// Initialize updater manager
await this.updaterManager.initialize();
@@ -340,9 +349,13 @@ export class App {
this.ipcClientEventMap.forEach((eventInfo, key) => {
const { controller, methodName } = eventInfo;
ipcMain.handle(key, async (e, ...data) => {
ipcMain.handle(key, async (e, data) => {
// 从 WebContents 获取对应的 BrowserWindow id
const senderIdentifier = this.browserManager.getIdentifierByWebContents(e.sender);
try {
return await controller[methodName](...data);
return await controller[methodName](data, {
identifier: senderIdentifier,
} as IpcClientEventSender);
} catch (error) {
logger.error(`Error handling IPC event ${key}:`, error);
return { error: error.message };
@@ -370,7 +383,13 @@ export class App {
// 新增 before-quit 处理函数
private handleBeforeQuit = () => {
this.isQuiting = true; // 首先设置标志
logger.info('Application is preparing to quit');
this.isQuiting = true;
// 销毁托盘
if (process.platform === 'win32') {
this.trayManager.destroyAll();
}
// 执行清理操作
this.unregisterAllRequestHandlers();

View File

@@ -184,12 +184,11 @@ export default class Browser {
const browserWindow = new BrowserWindow({
...res,
height: savedState?.height || height,
// Always create hidden first
show: false,
// Always create hidden first
title,
transparent: true,
@@ -199,11 +198,7 @@ export default class Browser {
// https://www.electronjs.org/docs/tutorial/context-isolation
contextIsolation: true,
preload: join(preloadDir, 'index.js'),
// devTools: isDev,
},
// Use saved state if available, otherwise use options. Do not set x/y
// x: savedState?.x, // Don't restore x
// y: savedState?.y, // Don't restore y
width: savedState?.width || width,
});
@@ -215,6 +210,7 @@ export default class Browser {
session: browserWindow.webContents.session,
});
console.log('platform:',process.platform);
// Windows 11 can use this new API
if (process.platform === 'win32' && browserWindow.setBackgroundMaterial) {
logger.debug(`[${this.identifier}] Setting window background material for Windows 11`);

View File

@@ -1,4 +1,5 @@
import { MainBroadcastEventKey, MainBroadcastParams } from '@lobechat/electron-client-ipc';
import { WebContents } from 'electron';
import { createLogger } from '@/utils/logger';
@@ -15,6 +16,8 @@ export default class BrowserManager {
browsers: Map<AppBrowsersIdentifiers, Browser> = new Map();
private webContentsMap = new Map<WebContents, AppBrowsersIdentifiers>();
constructor(app: App) {
logger.debug('Initializing BrowserManager');
this.app = app;
@@ -147,8 +150,40 @@ export default class BrowserManager {
logger.debug(`Creating new browser: ${options.identifier}`);
browser = new Browser(options, this.app);
this.browsers.set(options.identifier as AppBrowsersIdentifiers, browser);
const identifier = options.identifier as AppBrowsersIdentifiers;
this.browsers.set(identifier, browser);
// 记录 WebContents 和 identifier 的映射
this.webContentsMap.set(browser.browserWindow.webContents, identifier);
// 当窗口关闭时清理映射
browser.browserWindow.on('closed', () => {
this.webContentsMap.delete(browser.browserWindow.webContents);
});
return browser;
}
closeWindow(identifier: string) {
const browser = this.browsers.get(identifier as AppBrowsersIdentifiers);
browser?.close();
}
minimizeWindow(identifier: string) {
const browser = this.browsers.get(identifier as AppBrowsersIdentifiers);
browser?.browserWindow.minimize();
}
maximizeWindow(identifier: string) {
const browser = this.browsers.get(identifier as AppBrowsersIdentifiers);
if (browser.browserWindow.isMaximized()) {
browser?.browserWindow.unmaximize();
} else {
browser?.browserWindow.maximize();
}
}
getIdentifierByWebContents(webContents: WebContents): AppBrowsersIdentifiers | null {
return this.webContentsMap.get(webContents) || null;
}
}

View File

@@ -0,0 +1,231 @@
import { MainBroadcastEventKey, MainBroadcastParams } from '@lobechat/electron-client-ipc';
import {
DisplayBalloonOptions,
Tray as ElectronTray,
Menu,
MenuItemConstructorOptions,
app,
nativeImage,
} from 'electron';
import { join } from 'node:path';
import { resourcesDir } from '@/const/dir';
import { createLogger } from '@/utils/logger';
import type { App } from './App';
// 创建日志记录器
const logger = createLogger('core:Tray');
export interface TrayOptions {
/**
* 托盘图标路径(相对于资源目录)
*/
iconPath: string;
/**
* 托盘标识符
*/
identifier: string;
/**
* 托盘提示文本
*/
tooltip?: string;
}
export default class Tray {
private app: App;
/**
* 内部 Electron 托盘
*/
private _tray?: ElectronTray;
/**
* 标识符
*/
identifier: string;
/**
* 创建时的选项
*/
options: TrayOptions;
/**
* 获取托盘实例
*/
get tray() {
return this.retrieveOrInitialize();
}
/**
* 构造托盘对象
* @param options 托盘选项
* @param application 应用实例
*/
constructor(options: TrayOptions, application: App) {
logger.debug(`创建托盘实例: ${options.identifier}`);
logger.debug(`托盘选项: ${JSON.stringify(options)}`);
this.app = application;
this.identifier = options.identifier;
this.options = options;
// 初始化
this.retrieveOrInitialize();
}
/**
* 初始化托盘
*/
retrieveOrInitialize() {
// 如果托盘已存在且未被销毁,则返回
if (this._tray) {
logger.debug(`[${this.identifier}] 返回现有托盘实例`);
return this._tray;
}
const { iconPath, tooltip } = this.options;
// 加载托盘图标
logger.info(`创建新的托盘实例: ${this.identifier}`);
const iconFile = join(resourcesDir, iconPath);
logger.debug(`[${this.identifier}] 加载图标: ${iconFile}`);
try {
const icon = nativeImage.createFromPath(iconFile);
this._tray = new ElectronTray(icon);
// 设置工具提示
if (tooltip) {
logger.debug(`[${this.identifier}] 设置提示文本: ${tooltip}`);
this._tray.setToolTip(tooltip);
}
// 设置默认上下文菜单
this.setContextMenu();
// 设置点击事件
this._tray.on('click', () => {
logger.debug(`[${this.identifier}] 托盘被点击`);
this.onClick();
});
logger.debug(`[${this.identifier}] 托盘实例创建完成`);
return this._tray;
} catch (error) {
logger.error(`[${this.identifier}] 创建托盘失败:`, error);
throw error;
}
}
/**
* 设置托盘上下文菜单
* @param template 菜单模板,如果未提供则使用默认模板
*/
setContextMenu(template?: MenuItemConstructorOptions[]) {
logger.debug(`[${this.identifier}] 设置托盘上下文菜单`);
// 如果未提供模板,使用默认菜单
const defaultTemplate: MenuItemConstructorOptions[] = template || [
{
click: () => {
logger.debug(`[${this.identifier}] 菜单项 "显示主窗口" 被点击`);
this.app.browserManager.showMainWindow();
},
label: '显示主窗口',
},
{ type: 'separator' },
{
click: () => {
logger.debug(`[${this.identifier}] 菜单项 "退出" 被点击`);
app.quit();
},
label: '退出',
},
];
const contextMenu = Menu.buildFromTemplate(defaultTemplate);
this._tray?.setContextMenu(contextMenu);
logger.debug(`[${this.identifier}] 托盘上下文菜单已设置`);
}
/**
* 处理托盘点击事件
*/
onClick() {
logger.debug(`[${this.identifier}] 处理托盘点击事件`);
const mainWindow = this.app.browserManager.getMainWindow();
if (mainWindow) {
if (mainWindow.browserWindow.isVisible() && mainWindow.browserWindow.isFocused()) {
logger.debug(`[${this.identifier}] 主窗口已可见且聚焦,现在隐藏它`);
mainWindow.hide();
} else {
logger.debug(`[${this.identifier}] 显示并聚焦主窗口`);
mainWindow.show();
mainWindow.browserWindow.focus();
}
}
}
/**
* 更新托盘图标
* @param iconPath 新图标路径(相对于资源目录)
*/
updateIcon(iconPath: string) {
logger.debug(`[${this.identifier}] 更新图标: ${iconPath}`);
try {
const iconFile = join(resourcesDir, iconPath);
const icon = nativeImage.createFromPath(iconFile);
this._tray?.setImage(icon);
this.options.iconPath = iconPath;
logger.debug(`[${this.identifier}] 图标已更新`);
} catch (error) {
logger.error(`[${this.identifier}] 更新图标失败:`, error);
}
}
/**
* 更新提示文本
* @param tooltip 新提示文本
*/
updateTooltip(tooltip: string) {
logger.debug(`[${this.identifier}] 更新提示文本: ${tooltip}`);
this._tray?.setToolTip(tooltip);
this.options.tooltip = tooltip;
}
/**
* 显示气泡通知(仅在 Windows 上支持)
* @param options 气泡选项
*/
displayBalloon(options: DisplayBalloonOptions) {
if (process.platform === 'win32' && this._tray) {
logger.debug(`[${this.identifier}] 显示气泡通知: ${JSON.stringify(options)}`);
this._tray.displayBalloon(options);
} else {
logger.debug(`[${this.identifier}] 气泡通知仅在 Windows 上支持`);
}
}
/**
* 广播事件
*/
broadcast = <T extends MainBroadcastEventKey>(channel: T, data?: MainBroadcastParams<T>) => {
logger.debug(`向托盘 ${this.identifier} 广播, 频道: ${channel}`);
// 可以通过 App 实例的 browserManager 将消息转发到主窗口
this.app.browserManager.getMainWindow()?.broadcast(channel, data);
};
/**
* 销毁托盘实例
*/
destroy() {
logger.debug(`销毁托盘实例: ${this.identifier}`);
if (this._tray) {
this._tray.destroy();
this._tray = undefined;
}
}
}

View File

@@ -0,0 +1,131 @@
import { MainBroadcastEventKey, MainBroadcastParams } from '@lobechat/electron-client-ipc';
import { name } from '@/../../package.json';
import { createLogger } from '@/utils/logger';
import type { App } from './App';
import Tray, { TrayOptions } from './Tray';
// 创建日志记录器
const logger = createLogger('core:TrayManager');
/**
* 托盘标识符类型
*/
export type TrayIdentifiers = 'main';
export default class TrayManager {
app: App;
/**
* 存储所有托盘实例
*/
trays: Map<TrayIdentifiers, Tray> = new Map();
/**
* 构造方法
* @param app 应用实例
*/
constructor(app: App) {
logger.debug('初始化 TrayManager');
this.app = app;
}
/**
* 初始化所有托盘
*/
initializeTrays() {
logger.debug('初始化应用托盘');
// 初始化主托盘
this.initializeMainTray();
}
/**
* 获取主托盘
*/
getMainTray() {
return this.retrieveByIdentifier('main');
}
/**
* 初始化主托盘
*/
initializeMainTray() {
logger.debug('初始化主托盘');
return this.retrieveOrInitialize({
iconPath: 'tray-icon.png',
identifier: 'main', // 使用应用图标,需要确保资源目录中有此文件
tooltip: name, // 可以使用 app.getName() 或本地化字符串
});
}
/**
* 通过标识符获取托盘实例
* @param identifier 托盘标识符
*/
retrieveByIdentifier(identifier: TrayIdentifiers) {
logger.debug(`通过标识符获取托盘: ${identifier}`);
return this.trays.get(identifier);
}
/**
* 向所有托盘广播消息
* @param event 事件名称
* @param data 事件数据
*/
broadcastToAllTrays = <T extends MainBroadcastEventKey>(
event: T,
data: MainBroadcastParams<T>,
) => {
logger.debug(`向所有托盘广播事件 ${event}`);
this.trays.forEach((tray) => {
tray.broadcast(event, data);
});
};
/**
* 向指定托盘广播消息
* @param identifier 托盘标识符
* @param event 事件名称
* @param data 事件数据
*/
broadcastToTray = <T extends MainBroadcastEventKey>(
identifier: TrayIdentifiers,
event: T,
data: MainBroadcastParams<T>,
) => {
logger.debug(`向托盘 ${identifier} 广播事件 ${event}`);
this.trays.get(identifier)?.broadcast(event, data);
};
/**
* 获取或创建托盘实例
* @param options 托盘选项
*/
private retrieveOrInitialize(options: TrayOptions) {
let tray = this.trays.get(options.identifier as TrayIdentifiers);
if (tray) {
logger.debug(`获取现有托盘: ${options.identifier}`);
return tray;
}
logger.debug(`创建新托盘: ${options.identifier}`);
tray = new Tray(options, this.app);
this.trays.set(options.identifier as TrayIdentifiers, tray);
return tray;
}
/**
* 销毁所有托盘
*/
destroyAll() {
logger.debug('销毁所有托盘');
this.trays.forEach((tray) => {
tray.destroy();
});
this.trays.clear();
}
}

View File

@@ -0,0 +1,3 @@
export interface IpcClientEventSender {
identifier: string;
}

View File

@@ -3,6 +3,7 @@ import { MenuDispatchEvents } from './menu';
import { RemoteServerBroadcastEvents, RemoteServerDispatchEvents } from './remoteServer';
import { ShortcutDispatchEvents } from './shortcut';
import { SystemDispatchEvents } from './system';
import { TrayDispatchEvents } from './tray';
import { AutoUpdateBroadcastEvents, AutoUpdateDispatchEvents } from './update';
import { UploadFilesDispatchEvents } from './upload';
import { WindowsDispatchEvents } from './windows';
@@ -19,7 +20,8 @@ export interface ClientDispatchEvents
AutoUpdateDispatchEvents,
ShortcutDispatchEvents,
RemoteServerDispatchEvents,
UploadFilesDispatchEvents {}
UploadFilesDispatchEvents,
TrayDispatchEvents {}
export type ClientDispatchEventKey = keyof ClientDispatchEvents;

View File

@@ -2,7 +2,10 @@ import { ElectronAppState } from '../types';
export interface SystemDispatchEvents {
checkSystemAccessibility: () => boolean | undefined;
closeWindow: () => void;
getDesktopAppState: () => ElectronAppState;
maximizeWindow: () => void;
minimizeWindow: () => void;
openExternalLink: (url: string) => void;
/**
* 更新应用语言设置

View File

@@ -0,0 +1,31 @@
import {
ShowTrayNotificationParams,
UpdateTrayIconParams,
UpdateTrayTooltipParams,
} from '../types';
export interface TrayDispatchEvents {
/**
* 显示托盘通知
* @param params 通知参数
* @returns 操作结果
*/
showTrayNotification: (params: ShowTrayNotificationParams) => {
error?: string;
success: boolean;
};
/**
* 更新托盘图标
* @param params 图标参数
* @returns 操作结果
*/
updateTrayIcon: (params: UpdateTrayIconParams) => { error?: string; success: boolean };
/**
* 更新托盘提示文本
* @param params 提示文本参数
* @returns 操作结果
*/
updateTrayTooltip: (params: UpdateTrayTooltipParams) => { error?: string; success: boolean };
}

View File

@@ -5,5 +5,6 @@ export * from './proxyTRPCRequest';
export * from './route';
export * from './shortcut';
export * from './system';
export * from './tray';
export * from './update';
export * from './upload';

View File

@@ -0,0 +1,39 @@
/**
* 显示托盘通知的参数
*/
export interface ShowTrayNotificationParams {
/**
* 通知内容
*/
content: string;
/**
* 图标类型
*/
iconType?: 'info' | 'warning' | 'error' | 'none';
/**
* 通知标题
*/
title: string;
}
/**
* 更新托盘图标的参数
*/
export interface UpdateTrayIconParams {
/**
* 图标路径(相对于资源目录)
*/
iconPath: string;
}
/**
* 更新托盘提示文本的参数
*/
export interface UpdateTrayTooltipParams {
/**
* 提示文本
*/
tooltip: string;
}

View File

@@ -0,0 +1,85 @@
import { Icon } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { Minus, Square, XIcon } from 'lucide-react';
import { electronSystemService } from '@/services/electron/system';
import { TITLE_BAR_HEIGHT } from '../const';
const useStyles = createStyles(({ css, cx, token }) => {
const icon = css`
display: flex;
align-items: center;
justify-content: center;
width: 64px;
min-height: ${TITLE_BAR_HEIGHT}px;
transition: all ease-in-out 100ms;
-webkit-app-region: no-drag;
color: ${token.colorTextSecondary};
&:hover {
background: ${token.colorFillTertiary};
}
&:active {
background: ${token.colorFillSecondary};
}
`;
return {
close: cx(
icon,
css`
&:hover {
color: ${token.colorTextLightSolid};
/* win11 的色值,亮暗色均不变 */
background: #d33328;
}
&:active {
color: ${token.colorTextLightSolid};
/* win11 的色值 */
background: #8b2b25;
}
`,
),
container: css`
display: flex;
cursor: pointer;
`,
icon,
};
});
const WinControl = () => {
const { styles } = useStyles();
return (
<div className={styles.container}>
<div
className={styles.icon}
onClick={() => {
electronSystemService.minimizeWindow();
}}
>
<Icon icon={Minus} style={{ fontSize: 18 }} />
</div>
<div
className={styles.icon}
onClick={() => {
electronSystemService.maximizeWindow();
}}
>
<Icon icon={Square} />
</div>
<div
className={styles.close}
onClick={() => {
electronSystemService.closeWindow();
}}
>
<Icon icon={XIcon} style={{ fontSize: 18 }} />
</div>
</div>
);
};
export default WinControl;

View File

@@ -0,0 +1 @@
export const TITLE_BAR_HEIGHT = 36;

View File

@@ -1,14 +1,18 @@
import { Divider } from 'antd';
import { memo } from 'react';
import { Flexbox } from 'react-layout-kit';
import { useElectronStore } from '@/store/electron';
import { electronStylish } from '@/styles/electron';
import { isMacOS } from '@/utils/platform';
import Connection from './Connection';
import { UpdateModal } from './UpdateModal';
import { UpdateNotification } from './UpdateNotification';
import WinControl from './WinControl';
import { TITLE_BAR_HEIGHT } from './const';
export const TITLE_BAR_HEIGHT = 36;
const isMac = isMacOS();
const TitleBar = memo(() => {
const initElectronAppState = useElectronStore((s) => s.useInitElectronAppState);
@@ -22,16 +26,24 @@ const TitleBar = memo(() => {
height={TITLE_BAR_HEIGHT}
horizontal
justify={'space-between'}
paddingInline={12}
paddingInline={isMac ? 12 : '12px 0'}
style={{ minHeight: TITLE_BAR_HEIGHT }}
width={'100%'}
>
<div />
<div>{/* TODO */}</div>
<Flexbox className={electronStylish.nodrag} gap={8} horizontal>
<UpdateNotification />
<Connection />
<Flexbox align={'center'} gap={4} horizontal>
<Flexbox className={electronStylish.nodrag} gap={8} horizontal>
<UpdateNotification />
<Connection />
</Flexbox>
{!isMac && (
<>
<Divider type={'vertical'} />
<WinControl />
</>
)}
</Flexbox>
<UpdateModal />
</Flexbox>
@@ -39,3 +51,5 @@ const TitleBar = memo(() => {
});
export default TitleBar;
export { TITLE_BAR_HEIGHT } from './const';

View File

@@ -14,6 +14,18 @@ class ElectronSystemService {
return dispatch('getDesktopAppState');
}
async closeWindow(): Promise<void> {
return dispatch('closeWindow');
}
async maximizeWindow(): Promise<void> {
return dispatch('maximizeWindow');
}
async minimizeWindow(): Promise<void> {
return dispatch('minimizeWindow');
}
// Add other system-related service methods here if needed in the future
}