feat: 窗口尺寸位置与上次目录记忆、状态栏行数显示
This commit is contained in:
+50
-4
@@ -1,14 +1,44 @@
|
||||
import { join } from 'path'
|
||||
import { app, shell, BrowserWindow, Menu, ipcMain, dialog, clipboard, nativeTheme } from 'electron'
|
||||
import { join, dirname } from 'path'
|
||||
import { app, shell, BrowserWindow, Menu, ipcMain, dialog, clipboard, nativeTheme, screen } from 'electron'
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import iconv from 'iconv-lite'
|
||||
import fs from 'fs'
|
||||
import { parseState, clampBounds, type AppState } from './windowState'
|
||||
|
||||
/** 应用状态持久化文件(窗口 bounds + 上次文件目录),位于 userData 目录 */
|
||||
const stateFile = (): string => join(app.getPath('userData'), 'window-state.json')
|
||||
|
||||
/** 当前生效的应用状态(启动时从磁盘读取,运行中增量更新) */
|
||||
let appState: AppState = {}
|
||||
|
||||
/** 读取持久化状态:文件不存在/损坏一律回退空状态,不影响启动 */
|
||||
function loadState(): AppState {
|
||||
try {
|
||||
return parseState(fs.readFileSync(stateFile(), 'utf-8'))
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
/** 写回持久化状态:失败静默忽略(目录只读/磁盘满等,偏好记忆失效但不影响本次会话) */
|
||||
function saveState(): void {
|
||||
try {
|
||||
fs.writeFileSync(stateFile(), JSON.stringify(appState), 'utf-8')
|
||||
} catch {
|
||||
// 忽略写入失败
|
||||
}
|
||||
}
|
||||
|
||||
function createWindow(): void {
|
||||
// 恢复上次窗口尺寸/位置:越界 bounds 钳制回当前屏幕工作区,无记录时用默认尺寸
|
||||
const wa = screen.getPrimaryDisplay().workArea
|
||||
const bounds = appState.bounds ? clampBounds(appState.bounds, wa) : null
|
||||
// 主窗口
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 820,
|
||||
width: bounds?.width ?? 1280,
|
||||
height: bounds?.height ?? 820,
|
||||
x: bounds?.x,
|
||||
y: bounds?.y,
|
||||
minWidth: 900,
|
||||
minHeight: 600,
|
||||
show: false,
|
||||
@@ -24,6 +54,14 @@ function createWindow(): void {
|
||||
}
|
||||
})
|
||||
|
||||
// 关闭时记忆窗口普通状态 bounds(最大化/全屏/最小化时不记录,避免把超屏尺寸存进偏好)
|
||||
mainWindow.on('close', () => {
|
||||
if (mainWindow.isMinimized() || mainWindow.isMaximized() || mainWindow.isFullScreen()) return
|
||||
const b = mainWindow.getBounds()
|
||||
appState.bounds = { x: b.x, y: b.y, width: b.width, height: b.height }
|
||||
saveState()
|
||||
})
|
||||
|
||||
mainWindow.on('ready-to-show', () => {
|
||||
mainWindow.show()
|
||||
})
|
||||
@@ -98,6 +136,8 @@ function decodeText(buf: Buffer): { text: string; encoding: string; binary: bool
|
||||
ipcMain.handle('file:open', async (_event, side: 'left' | 'right' | null) => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
title: `选择${side === 'left' ? '左侧' : side === 'right' ? '右侧' : ''}文本文件`,
|
||||
// 记忆上次成功打开文件所在目录;无记录时传 undefined 走系统默认(最近使用位置)
|
||||
defaultPath: appState.lastDir,
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
// 与渲染进程 TEXT_EXTENSIONS(src/renderer/src/diff/textUtils.ts)完全对齐;两进程无法共享模块,改动需双向同步
|
||||
@@ -108,6 +148,9 @@ ipcMain.handle('file:open', async (_event, side: 'left' | 'right' | null) => {
|
||||
})
|
||||
if (result.canceled || result.filePaths.length === 0) return null
|
||||
const filePath = result.filePaths[0]
|
||||
// 记录所在目录供下次对话框起始定位(与窗口 bounds 同存一个状态文件)
|
||||
appState.lastDir = dirname(filePath)
|
||||
saveState()
|
||||
const name = filePath.split(/[\\/]/).pop() ?? filePath
|
||||
let buf: Buffer
|
||||
try {
|
||||
@@ -163,6 +206,9 @@ app.whenReady().then(() => {
|
||||
// 强制暗色系统主题:Windows 标题栏/滚动条等系统控件跟随应用暗色科幻风格
|
||||
nativeTheme.themeSource = 'dark'
|
||||
|
||||
// 读取持久化应用状态(窗口 bounds + 上次文件目录;损坏数据回退空状态)
|
||||
appState = loadState()
|
||||
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user