1376c2dee8
Audit-pass over CODE-AUDIT.md (~48 items closed this pass; all verified — typecheck + 234 tests + eslint + prettier green). Correctness / bugs: - B3: match the release checksum to the asset's filename line (no wrong-hash verify) - B4: newline-safe metadata probe (one --print with a unit-separator delimiter) - B5 / L88: guard the meta event against canceled items; progress no longer promotes a queued item outside pump() - B7: cookie-login promise always resolves (handles destroy-without-close) - L146: trim parser rejects >2 colon-group times; M36: Library selection counts only actionable rows - L11 / L50 / L156 / L57 / L159 / L15 / L3: live queue count, empty-cookie message, schedule picker min, dead-code/comment cleanup Type safety: - Enable noUncheckedIndexedAccess + noFallthroughCasesInSwitch (15 real edge cases fixed) Resilience / Windows / metadata: - R5: settings write failure handled (no unhandled IPC rejection; reconciles to truth) - W1 / W5 / W6: min window size, seeded folder picker, parented sign-in window; L147 dead macOS branches removed - CL1: shared stdout markers; package/builder metadata (license, homepage, repository, copyright, tsbuildinfo glob) Copy / docs / tests: - M37 / SR9 dev-jargon cleanup in hints; M8 / M25 / M26 / L66 / L80 / L81 reconciled - New unit tests for L35 (isValidMediaItem) and L36 (compareVersions) This commit also checkpoints the previously-uncommitted feat/tray-background-clipboard work it builds on: background running + auto-download, library clipboard detection, tray, binary management & library scale, credential encryption at rest, the shared jsonStore and ui/ primitives, and the eslint/prettier tooling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.5 KiB
TypeScript
68 lines
2.5 KiB
TypeScript
/**
|
||
* System tray + minimize-to-tray (Phase O). When Settings.minimizeToTray is on,
|
||
* closing the window hides it to the tray instead of quitting; the tray's "Quit"
|
||
* really exits. The tray gives the app a background presence (a natural home for
|
||
* the watched-source sync) and a quick show/quit menu.
|
||
*/
|
||
import { app, Tray, Menu, nativeImage, type BrowserWindow } from 'electron'
|
||
import { getAppIconPath } from './binaries'
|
||
|
||
// Fallback tray glyph (32×32 teal disc + white download arrow) used when no
|
||
// build/icon.ico is present. Without this the tray is skipped (an empty image
|
||
// makes an invisible/unclickable Windows tray entry), which would strand a
|
||
// minimized-to-tray window with no way back. Embedded as base64 so it needs no
|
||
// asset-bundling step.
|
||
const FALLBACK_TRAY_PNG =
|
||
'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAl0lEQVR4nO3TQQ6AIAxEUU7giV17bd0i' +
|
||
'aaDTmdqY0ISl/peirf11jvO6+/N5cHXKwlIIG6cQqngIoY5DiKy4C4G8aBwJohSArpIBmIgNKAWgDys' +
|
||
'AL8QGlANmCHZc8dUW1HEYEEFA6/d+B6q4CVAhwnHkb2DiUwCDkMRRBHpccQsRnXB8RLCAULxHMAAqbm0j5b4VoPRg1jzWxpzrS/JA7QAAAABJRU5ErkJggg=='
|
||
|
||
let tray: Tray | null = null
|
||
// True once the user has chosen to really quit (tray menu, or app.quit from the
|
||
// updater) — lets the window 'close' handler tell "hide to tray" from "exit".
|
||
let quitting = false
|
||
|
||
export function isQuitting(): boolean {
|
||
return quitting
|
||
}
|
||
|
||
export function markQuitting(): void {
|
||
quitting = true
|
||
}
|
||
|
||
function show(getWindow: () => BrowserWindow | null): void {
|
||
const win = getWindow()
|
||
if (!win) return
|
||
if (win.isMinimized()) win.restore()
|
||
win.show()
|
||
win.focus()
|
||
}
|
||
|
||
/** Create the tray icon + menu once. No-op if it already exists or the icon is missing. */
|
||
export function createTray(getWindow: () => BrowserWindow | null): void {
|
||
if (tray) return
|
||
// Prefer the real app icon; fall back to the embedded glyph when no icon.ico
|
||
// ships, so minimize-to-tray always has a tray to restore from.
|
||
let icon = nativeImage.createFromPath(getAppIconPath())
|
||
if (icon.isEmpty())
|
||
icon = nativeImage.createFromDataURL(`data:image/png;base64,${FALLBACK_TRAY_PNG}`)
|
||
if (icon.isEmpty()) return
|
||
|
||
tray = new Tray(icon)
|
||
tray.setToolTip('AeroFetch')
|
||
tray.setContextMenu(
|
||
Menu.buildFromTemplate([
|
||
{ label: 'Show AeroFetch', click: () => show(getWindow) },
|
||
{ type: 'separator' },
|
||
{
|
||
label: 'Quit AeroFetch',
|
||
click: () => {
|
||
quitting = true
|
||
app.quit()
|
||
}
|
||
}
|
||
])
|
||
)
|
||
tray.on('click', () => show(getWindow))
|
||
}
|