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