|
|
@@ -1,4 +1,4 @@
|
|
|
-import { app, shell, BrowserWindow, ipcMain, Tray, Menu, nativeImage, BrowserView, IpcMainEvent, dialog } from 'electron'
|
|
|
+import { app, shell, BrowserWindow, ipcMain, Tray, Menu, nativeImage, WebContentsView, IpcMainEvent, dialog } from 'electron'
|
|
|
import { join } from 'path'
|
|
|
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
|
|
import { mkdirSync, appendFileSync, existsSync, writeFileSync } from 'fs'
|
|
|
@@ -7,20 +7,23 @@ import https from 'https'
|
|
|
import http from 'http'
|
|
|
|
|
|
// --- 日志管理 ---
|
|
|
-const logDir = join(process.cwd(), 'logs')
|
|
|
-if (!existsSync(logDir)) {
|
|
|
- mkdirSync(logDir, { recursive: true })
|
|
|
+let logDir: string = ''
|
|
|
+
|
|
|
+function ensureLogDir(): void {
|
|
|
+ if (!logDir) {
|
|
|
+ logDir = join(app.getPath('userData'), 'logs')
|
|
|
+ }
|
|
|
+ if (!existsSync(logDir)) {
|
|
|
+ mkdirSync(logDir, { recursive: true })
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
function writeLog(message: string) {
|
|
|
+ ensureLogDir()
|
|
|
const date = new Date()
|
|
|
const fileName = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}.log`
|
|
|
const logPath = join(logDir, fileName)
|
|
|
-
|
|
|
- // Format: [HH:mm:ss] Message\n
|
|
|
- // If the message already has a timestamp from the renderer, we append it directly.
|
|
|
- // Renderer logger adds [timestamp] [level].
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
appendFileSync(logPath, message + '\n', 'utf-8')
|
|
|
} catch (err) {
|
|
|
@@ -45,21 +48,21 @@ interface TabInfo {
|
|
|
id: string
|
|
|
url: string
|
|
|
title: string
|
|
|
- view: BrowserView
|
|
|
+ view: WebContentsView
|
|
|
}
|
|
|
|
|
|
class ViewManager {
|
|
|
private window: BrowserWindow
|
|
|
private tabs: Map<string, TabInfo> = new Map()
|
|
|
private activeTabId: string | null = null
|
|
|
- private bounds: Electron.Rectangle = { x: 0, y: 78, width: 1024, height: 600 } // 初始默认值
|
|
|
+ private bounds: Electron.Rectangle = { x: 0, y: 78, width: 1024, height: 600 }
|
|
|
|
|
|
constructor(window: BrowserWindow) {
|
|
|
this.window = window
|
|
|
}
|
|
|
|
|
|
createTab(url: string, active: boolean = true): string {
|
|
|
- const view = new BrowserView({
|
|
|
+ const view = new WebContentsView({
|
|
|
webPreferences: {
|
|
|
sandbox: false,
|
|
|
nodeIntegration: false,
|
|
|
@@ -68,20 +71,19 @@ class ViewManager {
|
|
|
})
|
|
|
|
|
|
const id = Date.now().toString() + Math.random().toString(36).substr(2, 9)
|
|
|
-
|
|
|
- // 转发事件到渲染进程
|
|
|
+
|
|
|
view.webContents.on('did-start-loading', () => {
|
|
|
if (!this.window.isDestroyed()) {
|
|
|
this.window.webContents.send('tab-update', id, { isLoading: true })
|
|
|
}
|
|
|
})
|
|
|
-
|
|
|
+
|
|
|
view.webContents.on('did-stop-loading', () => {
|
|
|
if (!this.window.isDestroyed()) {
|
|
|
- this.window.webContents.send('tab-update', id, {
|
|
|
+ this.window.webContents.send('tab-update', id, {
|
|
|
isLoading: false,
|
|
|
- canGoBack: view.webContents.canGoBack(),
|
|
|
- canGoForward: view.webContents.canGoForward(),
|
|
|
+ canGoBack: view.webContents.navigationHistory.canGoBack(),
|
|
|
+ canGoForward: view.webContents.navigationHistory.canGoForward(),
|
|
|
url: view.webContents.getURL(),
|
|
|
title: view.webContents.getTitle()
|
|
|
})
|
|
|
@@ -93,15 +95,14 @@ class ViewManager {
|
|
|
this.window.webContents.send('tab-update', id, { title })
|
|
|
}
|
|
|
})
|
|
|
-
|
|
|
+
|
|
|
view.webContents.on('did-navigate', (_, url) => {
|
|
|
- if (!this.window.isDestroyed()) {
|
|
|
- this.window.webContents.send('tab-update', id, { url })
|
|
|
- }
|
|
|
+ if (!this.window.isDestroyed()) {
|
|
|
+ this.window.webContents.send('tab-update', id, { url })
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
view.webContents.setWindowOpenHandler((details) => {
|
|
|
- // 拦截新窗口,在当前窗口新建标签
|
|
|
const newId = this.createTab(details.url, true)
|
|
|
if (!this.window.isDestroyed()) {
|
|
|
this.window.webContents.send('tab-created', { id: newId, url: details.url, title: 'Loading...' })
|
|
|
@@ -110,11 +111,11 @@ class ViewManager {
|
|
|
})
|
|
|
|
|
|
try {
|
|
|
- view.webContents.loadURL(url)
|
|
|
+ view.webContents.loadURL(url)
|
|
|
} catch (e) {
|
|
|
- console.error('Failed to load URL:', url, e)
|
|
|
+ console.error('Failed to load URL:', url, e)
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
this.tabs.set(id, { id, url, title: 'Loading...', view })
|
|
|
|
|
|
if (active) {
|
|
|
@@ -128,19 +129,16 @@ class ViewManager {
|
|
|
const tab = this.tabs.get(id)
|
|
|
if (!tab) return
|
|
|
|
|
|
- // 移除旧视图
|
|
|
if (this.activeTabId) {
|
|
|
const currentTab = this.tabs.get(this.activeTabId)
|
|
|
if (currentTab) {
|
|
|
- this.window.removeBrowserView(currentTab.view)
|
|
|
+ this.window.contentView.removeChildView(currentTab.view)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 设置新视图
|
|
|
- this.window.setBrowserView(tab.view)
|
|
|
+ this.window.contentView.addChildView(tab.view)
|
|
|
tab.view.setBounds(this.bounds)
|
|
|
- tab.view.setAutoResize({ width: true, height: true })
|
|
|
-
|
|
|
+
|
|
|
this.activeTabId = id
|
|
|
}
|
|
|
|
|
|
@@ -149,13 +147,13 @@ class ViewManager {
|
|
|
if (!tab) return
|
|
|
|
|
|
if (this.activeTabId === id) {
|
|
|
- this.window.removeBrowserView(tab.view)
|
|
|
+ this.window.contentView.removeChildView(tab.view)
|
|
|
this.activeTabId = null
|
|
|
+ } else {
|
|
|
+ this.window.contentView.removeChildView(tab.view)
|
|
|
}
|
|
|
-
|
|
|
- // 销毁
|
|
|
- // 官方文档推荐用这种方式销毁 BrowserView 的内容
|
|
|
- (tab.view.webContents as any).destroy()
|
|
|
+
|
|
|
+ tab.view.webContents.close()
|
|
|
this.tabs.delete(id)
|
|
|
}
|
|
|
|
|
|
@@ -170,31 +168,32 @@ class ViewManager {
|
|
|
}
|
|
|
|
|
|
goBack() {
|
|
|
- if (this.activeTabId) this.tabs.get(this.activeTabId)?.view.webContents.goBack()
|
|
|
+ if (this.activeTabId) this.tabs.get(this.activeTabId)?.view.webContents.navigationHistory.goBack()
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
goForward() {
|
|
|
- if (this.activeTabId) this.tabs.get(this.activeTabId)?.view.webContents.goForward()
|
|
|
+ if (this.activeTabId) this.tabs.get(this.activeTabId)?.view.webContents.navigationHistory.goForward()
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
reload() {
|
|
|
if (this.activeTabId) this.tabs.get(this.activeTabId)?.view.webContents.reload()
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
loadURL(id: string, url: string) {
|
|
|
const tab = this.tabs.get(id)
|
|
|
if (tab) {
|
|
|
- tab.view.webContents.loadURL(url)
|
|
|
+ tab.view.webContents.loadURL(url)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
destroy() {
|
|
|
- this.tabs.forEach(tab => {
|
|
|
- try {
|
|
|
- (tab.view.webContents as any).destroy()
|
|
|
- } catch(e) {}
|
|
|
- })
|
|
|
- this.tabs.clear()
|
|
|
+ this.tabs.forEach(tab => {
|
|
|
+ try {
|
|
|
+ this.window.contentView.removeChildView(tab.view)
|
|
|
+ tab.view.webContents.close()
|
|
|
+ } catch (e) { /* already destroyed */ }
|
|
|
+ })
|
|
|
+ this.tabs.clear()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -311,7 +310,7 @@ function createWindow(): void {
|
|
|
titleBarStyle: 'hidden', // Hide title bar
|
|
|
titleBarOverlay: {
|
|
|
color: '#ffffff', // Match login background
|
|
|
- symbolColor: '#747474', // Match WeChat control color
|
|
|
+ symbolColor: '#747474', // Match control color
|
|
|
height: 30
|
|
|
},
|
|
|
webPreferences: {
|
|
|
@@ -1014,8 +1013,22 @@ ipcMain.on('browser-action', (event, action) => {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
+const gotTheLock = app.requestSingleInstanceLock()
|
|
|
+
|
|
|
+if (!gotTheLock) {
|
|
|
+ app.quit()
|
|
|
+} else {
|
|
|
+
|
|
|
+app.on('second-instance', () => {
|
|
|
+ if (mainWindow) {
|
|
|
+ if (mainWindow.isMinimized()) mainWindow.restore()
|
|
|
+ if (!mainWindow.isVisible()) mainWindow.show()
|
|
|
+ mainWindow.focus()
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
app.whenReady().then(() => {
|
|
|
- electronApp.setAppUserModelId('com.electron')
|
|
|
+ electronApp.setAppUserModelId('com.hnyunzhu.im')
|
|
|
app.on('browser-window-created', (_, window) => {
|
|
|
optimizer.watchWindowShortcuts(window)
|
|
|
})
|
|
|
@@ -1291,3 +1304,5 @@ app.on('window-all-closed', () => {
|
|
|
app.quit()
|
|
|
}
|
|
|
})
|
|
|
+
|
|
|
+} // end of gotTheLock else
|