feat(win): W2/UX19 persist window placement, W10 taskbar flash; tick W11
- W2/UX19: windowState.ts persists the window's normal bounds + maximized flag to <userData>/window-state.json (no new dep); createWindow restores from it with a screen.getAllDisplays() visibility guard so it never reopens off a disconnected monitor. Debounced save on resize/move + save on close (which may hide to tray). - W10: notify() flashFrame(true)s the taskbar when a completion/failure lands while the window is unfocused/minimized (Windows clears the flash on focus). - W11: already implemented (badge.ts overlay dot + setOverlayIcon) — ticked. typecheck + 268 tests + eslint + prettier green. (Window restore + flash want a live quit/relaunch confirm.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+24
-2
@@ -22,6 +22,7 @@ import { extractIncomingUrl, registerSendToShortcut, focusWindow } from './deepl
|
||||
import { isSyncLaunch } from './schedule'
|
||||
import { createTray, markQuitting, isQuitting } from './tray'
|
||||
import { logger } from './logger'
|
||||
import { initialWindowState, saveWindowState } from './windowState'
|
||||
|
||||
// Only one instance ever runs. A second launch — e.g. the OS invoking us again
|
||||
// for an aerofetch:// link or a "Send to AeroFetch" file — hands its argv to
|
||||
@@ -94,10 +95,16 @@ const DENIED_PERMISSIONS = new Set([
|
||||
])
|
||||
|
||||
function createWindow(): void {
|
||||
// Reopen where the user left off (size / position / maximized), falling back to the
|
||||
// default size when there's no usable saved state — first run, or the saved monitor
|
||||
// is no longer connected (W2 / UX19).
|
||||
const ws = initialWindowState()
|
||||
const win = new BrowserWindow({
|
||||
title: 'AeroFetch',
|
||||
width: 920,
|
||||
height: 700,
|
||||
width: ws.width,
|
||||
height: ws.height,
|
||||
x: ws.x,
|
||||
y: ws.y,
|
||||
// Below this the 212px sidebar + content layout breaks; pin a sensible
|
||||
// floor so the window can't be dragged down to unusable widths (W1).
|
||||
minWidth: 640,
|
||||
@@ -114,6 +121,19 @@ function createWindow(): void {
|
||||
})
|
||||
mainWindow = win
|
||||
|
||||
if (ws.maximized) win.maximize()
|
||||
|
||||
// Persist size/position/maximized so the next launch restores them (W2). Debounced
|
||||
// so a drag-resize writes once when it settles; also saved on close (which may only
|
||||
// hide to tray) to capture the final state.
|
||||
let saveTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const scheduleSave = (): void => {
|
||||
if (saveTimer) clearTimeout(saveTimer)
|
||||
saveTimer = setTimeout(() => saveWindowState(win), 500)
|
||||
}
|
||||
win.on('resize', scheduleSave)
|
||||
win.on('move', scheduleSave)
|
||||
|
||||
// Standard Cut/Copy/Paste/Select All right-click menu on editable fields (W4).
|
||||
attachEditContextMenu(win.webContents)
|
||||
|
||||
@@ -129,6 +149,8 @@ function createWindow(): void {
|
||||
// kill the spawned yt-dlp processes and lose the download. A real quit (tray
|
||||
// menu / before-quit) sets isQuitting() so this lets the close through.
|
||||
win.on('close', (e) => {
|
||||
// Capture the final bounds even when the close only hides to tray (W2).
|
||||
saveWindowState(win)
|
||||
if (isQuitting()) return
|
||||
const downloadsRunning = hasActiveDownloads()
|
||||
if (getSettings().minimizeToTray || downloadsRunning) {
|
||||
|
||||
Reference in New Issue
Block a user