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:
@@ -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", () => {
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
const electron = require("electron");
|
||||
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";
|
||||
@@ -69,11 +65,11 @@ const api = {
|
||||
external: {
|
||||
open: (url) => electron.ipcRenderer.invoke("external-open", url)
|
||||
},
|
||||
closeWindow: () => electron.ipcRenderer.send(IPC_EVENTS.WINDOW_CLOSE),
|
||||
minimizeWindow: () => electron.ipcRenderer.send(IPC_EVENTS.WINDOW_MINIMIZE),
|
||||
maximizeWindow: () => electron.ipcRenderer.send(IPC_EVENTS.WINDOW_MAXIMIZE),
|
||||
onWindowMaximized: (callback) => electron.ipcRenderer.on(IPC_EVENTS.WINDOW_MAXIMIZE + "back", (_, isMaximized) => callback(isMaximized)),
|
||||
isWindowMaximized: () => electron.ipcRenderer.invoke(IPC_EVENTS.IS_WINDOW_MAXIMIZED),
|
||||
platform: process.platform,
|
||||
windowMinimize: () => electron.ipcRenderer.invoke("window:minimize"),
|
||||
windowMaximize: () => electron.ipcRenderer.invoke("window:maximize"),
|
||||
windowClose: () => electron.ipcRenderer.invoke("window:close"),
|
||||
windowIsMaximized: () => electron.ipcRenderer.invoke("window:isMaximized"),
|
||||
viewIsReady: () => electron.ipcRenderer.send(IPC_EVENTS.RENDERER_IS_READY),
|
||||
app: {
|
||||
setFrameless: (route) => electron.ipcRenderer.invoke(IPC_EVENTS.APP_SET_FRAMELESS, route),
|
||||
|
||||
Reference in New Issue
Block a user