11import path from "node:path" ;
22import { fileURLToPath } from "node:url" ;
33import { createIPCHandler } from "@posthog/electron-trpc/main" ;
4- import { app , BrowserWindow , shell } from "electron" ;
4+ import { app , BrowserWindow , screen , shell } from "electron" ;
55import { buildApplicationMenu } from "./menu.js" ;
66import { setMainWindowGetter } from "./trpc/context.js" ;
77import { trpcRouter } from "./trpc/router.js" ;
8+ import { type WindowStateSchema , windowStateStore } from "./utils/store.js" ;
89
910declare const MAIN_WINDOW_VITE_DEV_SERVER_URL : string | undefined ;
1011declare const MAIN_WINDOW_VITE_NAME : string ;
1112
1213const __filename = fileURLToPath ( import . meta. url ) ;
1314const __dirname = path . dirname ( __filename ) ;
1415
16+ function isPositionOnScreen ( x : number , y : number ) : boolean {
17+ const displays = screen . getAllDisplays ( ) ;
18+ return displays . some ( ( display ) => {
19+ const { x : dx , y : dy , width, height } = display . bounds ;
20+ return x >= dx && x < dx + width && y >= dy && y < dy + height ;
21+ } ) ;
22+ }
23+
24+ function getSavedWindowState ( ) : WindowStateSchema {
25+ const state = {
26+ x : windowStateStore . get ( "x" ) ,
27+ y : windowStateStore . get ( "y" ) ,
28+ width : windowStateStore . get ( "width" , 1200 ) ,
29+ height : windowStateStore . get ( "height" , 600 ) ,
30+ isMaximized : windowStateStore . get ( "isMaximized" , true ) ,
31+ } ;
32+
33+ // Validate position is still on a connected display
34+ if ( state . x !== undefined && state . y !== undefined ) {
35+ if ( ! isPositionOnScreen ( state . x , state . y ) ) {
36+ state . x = undefined ;
37+ state . y = undefined ;
38+ }
39+ }
40+
41+ return state ;
42+ }
43+
44+ function saveWindowState ( window : BrowserWindow ) : void {
45+ const isMaximized = window . isMaximized ( ) ;
46+ windowStateStore . set ( "isMaximized" , isMaximized ) ;
47+
48+ // Only save bounds when not maximized, so restoring from maximized
49+ // gives the user their previous windowed size/position
50+ if ( ! isMaximized ) {
51+ const bounds = window . getBounds ( ) ;
52+ windowStateStore . set ( "x" , bounds . x ) ;
53+ windowStateStore . set ( "y" , bounds . y ) ;
54+ windowStateStore . set ( "width" , bounds . width ) ;
55+ windowStateStore . set ( "height" , bounds . height ) ;
56+ }
57+ }
58+
1559let mainWindow : BrowserWindow | null = null ;
1660
1761export function getMainWindow ( ) : BrowserWindow | null {
@@ -42,11 +86,28 @@ function setupExternalLinkHandlers(window: BrowserWindow): void {
4286
4387export function createWindow ( ) : void {
4488 const isDev = ! app . isPackaged ;
89+ const savedState = getSavedWindowState ( ) ;
90+ let saveTimeout : ReturnType < typeof setTimeout > | null = null ;
91+
92+ const scheduleSaveWindowState = ( window : BrowserWindow ) : void => {
93+ if ( saveTimeout ) {
94+ clearTimeout ( saveTimeout ) ;
95+ }
96+
97+ saveTimeout = setTimeout ( ( ) => {
98+ if ( ! window . isDestroyed ( ) ) {
99+ saveWindowState ( window ) ;
100+ }
101+ saveTimeout = null ;
102+ } , 200 ) ;
103+ } ;
45104
46105 mainWindow = new BrowserWindow ( {
47- width : 900 ,
48- height : 600 ,
49- minWidth : 900 ,
106+ ...( savedState . x !== undefined && { x : savedState . x } ) ,
107+ ...( savedState . y !== undefined && { y : savedState . y } ) ,
108+ width : savedState . width ,
109+ height : savedState . height ,
110+ minWidth : 1200 ,
50111 minHeight : 600 ,
51112 backgroundColor : "#0a0a0a" ,
52113 titleBarStyle : "hiddenInset" ,
@@ -63,10 +124,25 @@ export function createWindow(): void {
63124 } ) ;
64125
65126 mainWindow . once ( "ready-to-show" , ( ) => {
66- mainWindow ?. maximize ( ) ;
127+ if ( savedState . isMaximized ) {
128+ mainWindow ?. maximize ( ) ;
129+ }
67130 mainWindow ?. show ( ) ;
68131 } ) ;
69132
133+ // Persist window state on changes
134+ mainWindow . on (
135+ "resize" ,
136+ ( ) => mainWindow && scheduleSaveWindowState ( mainWindow ) ,
137+ ) ;
138+ mainWindow . on (
139+ "move" ,
140+ ( ) => mainWindow && scheduleSaveWindowState ( mainWindow ) ,
141+ ) ;
142+ mainWindow . on ( "maximize" , ( ) => mainWindow && saveWindowState ( mainWindow ) ) ;
143+ mainWindow . on ( "unmaximize" , ( ) => mainWindow && saveWindowState ( mainWindow ) ) ;
144+ mainWindow . on ( "close" , ( ) => mainWindow && saveWindowState ( mainWindow ) ) ;
145+
70146 setMainWindowGetter ( ( ) => mainWindow ) ;
71147
72148 createIPCHandler ( {
@@ -86,6 +162,10 @@ export function createWindow(): void {
86162 }
87163
88164 mainWindow . on ( "closed" , ( ) => {
165+ if ( saveTimeout ) {
166+ clearTimeout ( saveTimeout ) ;
167+ saveTimeout = null ;
168+ }
89169 mainWindow = null ;
90170 } ) ;
91171}
0 commit comments