feat: Persist main window size, position, and maximization

This commit is contained in:
2026-06-27 23:29:48 +07:00
parent c8317eb02a
commit aeff519ae0
3 changed files with 75 additions and 4 deletions
+4 -4
View File
@@ -108,7 +108,9 @@ const handlers = {
"debug:repoFlush": (name) => guard(async () => debug.repoFlush(name)), "debug:repoFlush": (name) => guard(async () => debug.repoFlush(name)),
"debug:openWindow": () => guard(async () => openDebugWindow()), "debug:openWindow": () => guard(async () => openDebugWindow()),
} satisfies { } satisfies {
[C in IpcRequestChannel]: (...args: Parameters<IpcRequests[C]>) => Promise<ReturnType<IpcRequests[C]>>; [C in IpcRequestChannel]: (
...args: Parameters<IpcRequests[C]>
) => Promise<ReturnType<IpcRequests[C]>>;
}; };
export function registerIpcHandlers(): void { export function registerIpcHandlers(): void {
@@ -121,7 +123,5 @@ function register<C extends IpcRequestChannel>(channel: C): void {
const handler = handlers[channel] as ( const handler = handlers[channel] as (
...args: Parameters<IpcRequests[C]> ...args: Parameters<IpcRequests[C]>
) => Promise<ReturnType<IpcRequests[C]>>; ) => Promise<ReturnType<IpcRequests[C]>>;
ipcMain.handle(channel, (_event, ...args) => ipcMain.handle(channel, (_event, ...args) => handler(...(args as Parameters<IpcRequests[C]>)));
handler(...(args as Parameters<IpcRequests[C]>)),
);
} }
+65
View File
@@ -0,0 +1,65 @@
import { app, screen, type BrowserWindow, type Rectangle } from "electron";
import { join } from "node:path";
import { readFileSync } from "node:fs";
import { writeFileAtomicSync } from "./lib/atomicFile";
type Bounds = Rectangle & { maximized: boolean };
const path = () => join(app.getPath("userData"), "window-state.json");
function read(): Partial<Bounds> | null {
try {
return JSON.parse(readFileSync(path(), "utf8")) as Partial<Bounds>;
} catch {
return null;
}
}
function onScreen(b: Rectangle): boolean {
return screen.getAllDisplays().some((d) => {
const w = d.workArea;
return (
b.x < w.x + w.width && b.x + b.width > w.x && b.y < w.y + w.height && b.y + b.height > w.y
);
});
}
export function restoreBounds(): { bounds?: Rectangle; maximized: boolean } {
const saved = read();
if (
saved &&
saved.width &&
saved.height &&
saved.x != null &&
saved.y != null &&
onScreen({ x: saved.x, y: saved.y, width: saved.width, height: saved.height })
) {
return {
bounds: { x: saved.x, y: saved.y, width: saved.width, height: saved.height },
maximized: saved.maximized ?? false,
};
}
return { maximized: saved?.maximized ?? false };
}
export function trackBounds(win: BrowserWindow): void {
let normalBounds = win.getBounds();
const save = (): void => {
if (win.isDestroyed()) return;
writeFileAtomicSync(
path(),
JSON.stringify({ ...normalBounds, maximized: win.isMaximized() }, null, 2),
);
};
const remember = (): void => {
if (!win.isMaximized() && !win.isMinimized() && !win.isFullScreen()) {
normalBounds = win.getBounds();
}
};
win.on("resize", remember);
win.on("move", remember);
win.on("close", save);
}
+6
View File
@@ -3,6 +3,7 @@ import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import type { IpcEventChannel, IpcEvents } from "../shared/ipc"; import type { IpcEventChannel, IpcEvents } from "../shared/ipc";
import { TRAFFIC_LIGHT_INSET } from "../shared/window"; import { TRAFFIC_LIGHT_INSET } from "../shared/window";
import { restoreBounds, trackBounds } from "./windowState";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
const isDev = !app.isPackaged; const isDev = !app.isPackaged;
@@ -25,9 +26,11 @@ function load(win: BrowserWindow, hash = ""): void {
} }
export function createMainWindow(): BrowserWindow { export function createMainWindow(): BrowserWindow {
const restored = restoreBounds();
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 1180, width: 1180,
height: 760, height: 760,
...restored.bounds,
minWidth: 940, minWidth: 940,
minHeight: 600, minHeight: 600,
show: false, show: false,
@@ -43,6 +46,9 @@ export function createMainWindow(): BrowserWindow {
}, },
}); });
if (restored.maximized) mainWindow.maximize();
trackBounds(mainWindow);
mainWindow.once("ready-to-show", () => mainWindow?.show()); mainWindow.once("ready-to-show", () => mainWindow?.show());
mainWindow.webContents.setWindowOpenHandler(({ url }) => { mainWindow.webContents.setWindowOpenHandler(({ url }) => {
void shell.openExternal(url); void shell.openExternal(url);