返回 oh-my-ppt
window.ts
根目录 / src / main / app / window.ts
1 import { app, BrowserWindow, screen, shell, type Size } from 'electron'
2 import { is } from '@electron-toolkit/utils'
3 import log from 'electron-log/main.js'
4 import { existsSync } from 'fs'
5 import { dirname, join } from 'path'
6 import { fileURLToPath } from 'url'
7 import { attachRendererCrashRecovery } from './lifecycle'
8 import { configureWindowMenu } from './menu'
9
10 const APP_NAME = 'OhMyPPT'
11 const DEFAULT_WINDOW_WIDTH = 1280
12 const DEFAULT_WINDOW_HEIGHT = 820
13 const BASE_MIN_WIDTH = 880
14 const BASE_MIN_HEIGHT = 680
15 const TITLEBAR_HEIGHT = 48
16 const TITLEBAR_BACKGROUND = '#f4eddf'
17 const __dirname = dirname(fileURLToPath(import.meta.url))
18 // electron-vite bundles this module into out/main/index.js. Keep asset paths
19 // relative to that runtime location rather than this source file's directory.
20 const mainOutputDir = __dirname
21
22 const resolveWindowBounds = (): {
23 width: number
24 height: number
25 minWidth: number
26 minHeight: number
27 workArea: Size
28 } => {
29 const workArea = screen.getPrimaryDisplay().workAreaSize
30 const maxInitialWidth = Math.max(900, workArea.width - 72)
31 const maxInitialHeight = Math.max(620, workArea.height - 88)
32 const minWidth = Math.min(BASE_MIN_WIDTH, maxInitialWidth)
33 const minHeight = Math.min(BASE_MIN_HEIGHT, maxInitialHeight)
34 const width = Math.max(minWidth, Math.min(DEFAULT_WINDOW_WIDTH, maxInitialWidth))
35 const height = Math.max(minHeight, Math.min(DEFAULT_WINDOW_HEIGHT, maxInitialHeight))
36
37 return { width, height, minWidth, minHeight, workArea }
38 }
39
40 export type MainWindowOptions = {
41 isShuttingDown(): boolean
42 isTrayEnabled(): boolean
43 onHideToTray(): void
44 }
45
46 export function createMainWindow(options: MainWindowOptions): BrowserWindow {
47 const isMac = process.platform === 'darwin'
48 const preloadPath = join(mainOutputDir, '../preload/index.mjs')
49 const windowBounds = resolveWindowBounds()
50 const iconPath = join(mainOutputDir, '../../build/icons/512x512.png')
51
52 if (isMac && existsSync(iconPath)) {
53 try {
54 app.dock?.setIcon(iconPath)
55 } catch {
56 // Ignore a platform-specific dock icon failure.
57 }
58 }
59
60 const window = new BrowserWindow({
61 title: APP_NAME,
62 width: windowBounds.width,
63 height: windowBounds.height,
64 minWidth: windowBounds.minWidth,
65 minHeight: windowBounds.minHeight,
66 center: true,
67 show: false,
68 backgroundColor: TITLEBAR_BACKGROUND,
69 ...(existsSync(iconPath) ? { icon: iconPath } : {}),
70 ...(isMac
71 ? {
72 titleBarStyle: 'hidden',
73 trafficLightPosition: { x: 14, y: Math.round((TITLEBAR_HEIGHT - 14) / 2) }
74 }
75 : {
76 frame: false
77 }),
78 webPreferences: {
79 preload: preloadPath,
80 sandbox: false,
81 contextIsolation: true,
82 nodeIntegration: false,
83 webSecurity: true,
84 webviewTag: true
85 }
86 })
87 configureWindowMenu(window)
88
89 window.on('close', (event) => {
90 if (process.platform === 'win32' && options.isTrayEnabled() && !options.isShuttingDown()) {
91 event.preventDefault()
92 window.hide()
93 options.onHideToTray()
94 }
95 })
96
97 log.info('[app] creating window', {
98 preloadPath,
99 contextIsolation: true,
100 sandbox: false,
101 window: {
102 width: windowBounds.width,
103 height: windowBounds.height,
104 minWidth: windowBounds.minWidth,
105 minHeight: windowBounds.minHeight,
106 workArea: windowBounds.workArea,
107 titlebarHeight: TITLEBAR_HEIGHT,
108 titleBarStyle: isMac ? 'hidden' : 'frameless'
109 }
110 })
111
112 window.on('ready-to-show', () => window.show())
113 window.webContents.setWindowOpenHandler((details) => {
114 shell.openExternal(details.url)
115 return { action: 'deny' }
116 })
117
118 const loadHome = (): void => {
119 if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
120 const rendererUrl = new URL(process.env['ELECTRON_RENDERER_URL'])
121 rendererUrl.hash = '/'
122 void window.loadURL(rendererUrl.toString())
123 return
124 }
125 void window.loadFile(join(mainOutputDir, '../renderer/index.html'), { hash: '/' })
126 }
127 attachRendererCrashRecovery(window, { isShuttingDown: options.isShuttingDown, loadHome })
128
129 if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
130 void window.loadURL(process.env['ELECTRON_RENDERER_URL'])
131 } else {
132 void window.loadFile(join(mainOutputDir, '../renderer/index.html'))
133 }
134 return window
135 }
136
137 export function showMainWindow(window: BrowserWindow | null): void {
138 if (!window || window.isDestroyed()) return
139 if (window.isMinimized()) window.restore()
140 window.show()
141 window.focus()
142 }
143
143 lines TYPESCRIPT