feat: implement custom window controls and replace header bar with title bar

- Add window handlers for minimize, maximize, close, and check if maximized in ipcMain.
- Update preload script to use new window control IPC events.
- Refactor window service to remove old IPC event handlers and use new handlers.
- Remove old HeaderBar and DragRegion components, replacing them with a new TitleBar component.
- Update Layout component to use TitleBar instead of HeaderBar.
- Remove useWinManager hook as its functionality is now integrated into TitleBar.
- Update login page to remove HeaderBar and adjust layout accordingly.
- Update constants to remove old window IPC events.
- Update package dependencies to replace @iconify/vue with @lucide/vue.
This commit is contained in:
duanshuwen
2026-04-14 23:38:42 +08:00
parent 6fd51d04dd
commit b5a67ff650
20 changed files with 642 additions and 340 deletions

View File

@@ -22,11 +22,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
mod
));
const electron = require("electron");
require("js-base64");
const util = require("util");
const log = require("electron-log");
const path = require("path");
const fs = require("fs");
require("js-base64");
const path$1 = require("node:path");
const crypto = require("crypto");
const started = require("electron-squirrel-startup");
@@ -58,10 +58,6 @@ const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
var IPC_EVENTS = /* @__PURE__ */ ((IPC_EVENTS2) => {
IPC_EVENTS2["EXTERNAL_OPEN"] = "external-open";
IPC_EVENTS2["WINDOW_MINIMIZE"] = "window-minimize";
IPC_EVENTS2["WINDOW_MAXIMIZE"] = "window-maximize";
IPC_EVENTS2["WINDOW_CLOSE"] = "window-close";
IPC_EVENTS2["IS_WINDOW_MAXIMIZED"] = "is-window-maximized";
IPC_EVENTS2["APP_SET_FRAMELESS"] = "app:set-frameless";
IPC_EVENTS2["APP_LOAD_PAGE"] = "app:load-page";
IPC_EVENTS2["TAB_CREATE"] = "tab:create";
@@ -175,40 +171,6 @@ var MESSAGE_ITEM_MENU_IDS = /* @__PURE__ */ ((MESSAGE_ITEM_MENU_IDS2) => {
MESSAGE_ITEM_MENU_IDS2["SELECT"] = "select";
return MESSAGE_ITEM_MENU_IDS2;
})(MESSAGE_ITEM_MENU_IDS || {});
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
function cloneDeep(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => cloneDeep(item));
}
const clone = Object.assign({}, obj);
for (const key in clone) {
if (Object.prototype.hasOwnProperty.call(clone, key)) {
clone[key] = cloneDeep(clone[key]);
}
}
return clone;
}
function simpleCloneDeep(obj) {
try {
return JSON.parse(JSON.stringify(obj));
} catch (error) {
console.error("simpleCloneDeep failed:", error);
return obj;
}
}
const readdirAsync = util.promisify(fs__namespace.readdir);
const statAsync = util.promisify(fs__namespace.stat);
const unlinkAsync = util.promisify(fs__namespace.unlink);
@@ -336,6 +298,40 @@ class LogService {
}
}
const logManager = LogService.getInstance();
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
function cloneDeep(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => cloneDeep(item));
}
const clone = Object.assign({}, obj);
for (const key in clone) {
if (Object.prototype.hasOwnProperty.call(clone, key)) {
clone[key] = cloneDeep(clone[key]);
}
}
return clone;
}
function simpleCloneDeep(obj) {
try {
return JSON.parse(JSON.stringify(obj));
} catch (error) {
console.error("simpleCloneDeep failed:", error);
return obj;
}
}
const DEFAULT_CONFIG = {
[CONFIG_KEYS.THEME_MODE]: "system",
[CONFIG_KEYS.PRIMARY_COLOR]: "#BB5BE7",
@@ -534,10 +530,13 @@ class ThemeService {
}
}
const themeManager = ThemeService.getInstance();
const isMac = process.platform === "darwin";
const isWindows = process.platform === "win32";
const useCustomTitleBar = isWindows;
const SHARED_WINDOW_OPTIONS = {
frame: false,
titleBarStyle: "hidden",
trafficLightPosition: { x: -100, y: -100 },
frame: isMac || !useCustomTitleBar,
titleBarStyle: isMac ? "hiddenInset" : useCustomTitleBar ? "hidden" : "default",
trafficLightPosition: isMac ? { x: 16, y: 16 } : void 0,
show: false,
title: "NIANXX",
darkTheme: themeManager.isDark,
@@ -574,24 +573,6 @@ class WindowService {
return true;
}
_setupIpcEvents() {
const handleCloseWindow = (e) => {
const target = electron.BrowserWindow.fromWebContents(e.sender);
const winName = this.getName(target);
this.close(target, this._isReallyClose(winName));
};
const handleMinimizeWindow = (e) => {
electron.BrowserWindow.fromWebContents(e.sender)?.minimize();
};
const handleMaximizeWindow = (e) => {
this.toggleMax(electron.BrowserWindow.fromWebContents(e.sender));
};
const handleIsWindowMaximized = (e) => {
return electron.BrowserWindow.fromWebContents(e.sender)?.isMaximized() ?? false;
};
electron.ipcMain.on(IPC_EVENTS.WINDOW_CLOSE, handleCloseWindow);
electron.ipcMain.on(IPC_EVENTS.WINDOW_MINIMIZE, handleMinimizeWindow);
electron.ipcMain.on(IPC_EVENTS.WINDOW_MAXIMIZE, handleMaximizeWindow);
electron.ipcMain.handle(IPC_EVENTS.IS_WINDOW_MAXIMIZED, handleIsWindowMaximized);
electron.ipcMain.handle(IPC_EVENTS.APP_LOAD_PAGE, (e, page) => {
const win = electron.BrowserWindow.fromWebContents(e.sender);
if (win) this._loadPage(win, page);
@@ -625,16 +606,13 @@ class WindowService {
return window2;
}
_setupWinLifecycle(window2, name) {
const updateWinStatus = debounce(() => !window2?.isDestroyed() && window2?.webContents?.send(IPC_EVENTS.WINDOW_MAXIMIZE + "back", window2?.isMaximized()), 80);
window2.once("closed", () => {
this._winStates[name].onClosed.forEach((callback) => callback(window2));
window2?.destroy();
window2?.removeListener("resize", updateWinStatus);
this._winStates[name].instance = void 0;
this._winStates[name].isHidden = false;
logManager.info(`Window closed: ${name}`);
});
window2.on("resize", updateWinStatus);
return this;
}
_listenWinReady(params) {
@@ -1092,6 +1070,24 @@ class TabManager {
};
}
}
function registerWindowHandlers(mainWindow) {
electron.ipcMain.handle("window:minimize", () => {
mainWindow.minimize();
});
electron.ipcMain.handle("window:maximize", () => {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
} else {
mainWindow.maximize();
}
});
electron.ipcMain.handle("window:close", () => {
mainWindow.close();
});
electron.ipcMain.handle("window:isMaximized", () => {
return mainWindow.isMaximized();
});
}
const handleTray = (minimizeToTray) => {
if (minimizeToTray) {
trayManager.create();
@@ -1184,6 +1180,7 @@ function setupMainWindow() {
});
handleTray(minimizeToTray);
registerMenus(mainWindow);
registerWindowHandlers(mainWindow);
const tabManager = new TabManager(mainWindow);
tabManager.enable();
mainWindow.on("closed", () => {