mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 18:50:56 +08:00
* refactor(main): 使用枚举管理 IPC 通道 - 新增 IpcChannel 枚举,用于统一管理所有的 IPC 通道 - 修改相关代码,使用 IpcChannel 枚举替代硬编码的字符串通道名称 - 此改动有助于提高代码的可维护性和可读性,避免因通道名称变更导致的错误 * refactor(ipc): 将字符串通道名称替换为 IpcChannel 枚举 - 在多个文件中将硬编码的字符串通道名称替换为 IpcChannel 枚举值 - 更新了相关文件的导入,增加了对 IpcChannel 的引用 - 通过使用枚举来管理 IPC 通道名称,提高了代码的可维护性和可读性 * refactor(ipc): 调整 IPC 通道枚举和预加载脚本 - 移除了 IpcChannel 枚举中的未使用注释 - 更新了预加载脚本中 IpcChannel 的导入路径 * refactor(ipc): 更新 IpcChannel导入路径 - 将 IpcChannel 的导入路径从 @main/enum/IpcChannel 修改为 @shared/IpcChannel - 此修改涉及多个文件,包括 AppUpdater、BackupManager、EditMcpJsonPopup 等 - 同时移除了 tsconfig.web.json 中对 src/main/**/* 的引用 * refactor(ipc): 添加 ReduxStoreReady 事件并更新事件监听 - 在 IpcChannel 枚举中添加 ReduxStoreReady 事件 - 更新 ReduxService 中的事件监听,使用新的枚举值 * refactor(main): 重构 ReduxService 中的状态变化事件处理 - 将状态变化事件名称定义为常量 STATUS_CHANGE_EVENT - 更新事件监听和触发使用新的常量 - 优化了代码结构,提高了可维护性 * refactor(i18n): 优化国际化配置和语言选择逻辑 - 在多个文件中引入 defaultLanguage 常量,统一默认语言设置 - 调整 i18n 初始化和语言变更逻辑,使用新配置 - 更新相关组件和 Hook 中的语言选择逻辑 * refactor(ConfigManager): 重构配置管理器 - 添加 ConfigKeys 枚举,用于统一配置项的键名 - 引入 defaultLanguage,作为默认语言设置 - 重构 get 和 set 方法,使用 ConfigKeys 枚举作为键名 - 优化类型定义和方法签名,提高代码可读性和可维护性 * refactor(ConfigManager): 重命名配置键 ZoomFactor 将配置键 zoomFactor 重命名为 ZoomFactor,以符合命名规范。 更新了相关方法和属性以反映这一变更。 * refactor(shared): 重构常量定义并优化文件大小格式化逻辑 - 在 constant.ts 中添加 KB、MB、GB 常量定义 - 将 defaultLanguage 移至 constant.ts - 更新 ConfigManager、useAppInit、i18n、GeneralSettings 等文件中的导入路径 - 优化 formatFileSize 函数,使用新定义的常量 * refactor(FileSize): 使用 GB/MB/KB 等常量处理文件大小计算 * refactor(ipc): 将字符串通道名称替换为 IpcChannel 枚举 - 在多个文件中将硬编码的字符串通道名称替换为 IpcChannel 枚举值 - 更新了相关文件的导入,增加了对 IpcChannel 的引用 - 通过使用枚举来管理 IPC 通道名称,提高了代码的可维护性和可读性 * refactor(ipc): 更新 IpcChannel导入路径 - 将 IpcChannel 的导入路径从 @main/enum/IpcChannel 修改为 @shared/IpcChannel - 此修改涉及多个文件,包括 AppUpdater、BackupManager、EditMcpJsonPopup 等 - 同时移除了 tsconfig.web.json 中对 src/main/**/* 的引用 * refactor(i18n): 优化国际化配置和语言选择逻辑 - 在多个文件中引入 defaultLanguage 常量,统一默认语言设置 - 调整 i18n 初始化和语言变更逻辑,使用新配置 - 更新相关组件和 Hook 中的语言选择逻辑 * refactor(shared): 重构常量定义并优化文件大小格式化逻辑 - 在 constant.ts 中添加 KB、MB、GB 常量定义 - 将 defaultLanguage 移至 constant.ts - 更新 ConfigManager、useAppInit、i18n、GeneralSettings 等文件中的导入路径 - 优化 formatFileSize 函数,使用新定义的常量 * refactor: 移除重复的导入语句 - 在 HomeWindow.tsx 和 useAppInit.ts 文件中移除了重复的 defaultLanguage导入语句 - 这个改动简化了代码结构,提高了代码的可读性和维护性
228 lines
6.1 KiB
TypeScript
228 lines
6.1 KiB
TypeScript
import { ipcMain } from 'electron'
|
|
import { EventEmitter } from 'events'
|
|
|
|
import { windowService } from './WindowService'
|
|
import { IpcChannel } from '@shared/IpcChannel'
|
|
|
|
type StoreValue = any
|
|
type Unsubscribe = () => void
|
|
|
|
export class ReduxService extends EventEmitter {
|
|
private stateCache: any = {}
|
|
private isReady = false
|
|
|
|
private readonly STATUS_CHANGE_EVENT = 'statusChange'
|
|
|
|
constructor() {
|
|
super()
|
|
this.setupIpcHandlers()
|
|
}
|
|
|
|
private setupIpcHandlers() {
|
|
// 监听 store 就绪事件
|
|
ipcMain.handle(IpcChannel.ReduxStoreReady, () => {
|
|
this.isReady = true
|
|
this.emit('ready')
|
|
})
|
|
|
|
// 监听 store 状态变化
|
|
ipcMain.on(IpcChannel.ReduxStateChange, (_, newState) => {
|
|
this.stateCache = newState
|
|
this.emit(this.STATUS_CHANGE_EVENT, newState)
|
|
})
|
|
}
|
|
|
|
private async waitForStoreReady(webContents: Electron.WebContents, timeout = 10000): Promise<void> {
|
|
if (this.isReady) return
|
|
|
|
const startTime = Date.now()
|
|
while (Date.now() - startTime < timeout) {
|
|
try {
|
|
const isReady = await webContents.executeJavaScript(`
|
|
!!window.store && typeof window.store.getState === 'function'
|
|
`)
|
|
if (isReady) {
|
|
this.isReady = true
|
|
return
|
|
}
|
|
} catch (error) {
|
|
// 忽略错误,继续等待
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
}
|
|
throw new Error('Timeout waiting for Redux store to be ready')
|
|
}
|
|
|
|
// 添加同步获取状态的方法
|
|
getStateSync() {
|
|
return this.stateCache
|
|
}
|
|
|
|
// 添加同步选择器方法
|
|
selectSync<T = StoreValue>(selector: string): T | undefined {
|
|
try {
|
|
// 使用 Function 构造器来安全地执行选择器
|
|
const selectorFn = new Function('state', `return ${selector}`)
|
|
return selectorFn(this.stateCache)
|
|
} catch (error) {
|
|
console.error('Failed to select from cache:', error)
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
// 修改 select 方法,优先使用缓存
|
|
async select<T = StoreValue>(selector: string): Promise<T> {
|
|
try {
|
|
// 如果已经准备就绪,先尝试从缓存中获取
|
|
if (this.isReady) {
|
|
const cachedValue = this.selectSync<T>(selector)
|
|
if (cachedValue !== undefined) {
|
|
return cachedValue
|
|
}
|
|
}
|
|
|
|
// 如果缓存中没有,再从渲染进程获取
|
|
const mainWindow = windowService.getMainWindow()
|
|
if (!mainWindow) {
|
|
throw new Error('Main window is not available')
|
|
}
|
|
await this.waitForStoreReady(mainWindow.webContents)
|
|
return await mainWindow.webContents.executeJavaScript(`
|
|
(() => {
|
|
const state = window.store.getState();
|
|
return ${selector};
|
|
})()
|
|
`)
|
|
} catch (error) {
|
|
console.error('Failed to select store value:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// 派发 action
|
|
async dispatch(action: any): Promise<void> {
|
|
const mainWindow = windowService.getMainWindow()
|
|
if (!mainWindow) {
|
|
throw new Error('Main window is not available')
|
|
}
|
|
await this.waitForStoreReady(mainWindow.webContents)
|
|
try {
|
|
await mainWindow.webContents.executeJavaScript(`
|
|
window.store.dispatch(${JSON.stringify(action)})
|
|
`)
|
|
} catch (error) {
|
|
console.error('Failed to dispatch action:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// 订阅状态变化
|
|
async subscribe(selector: string, callback: (newValue: any) => void): Promise<Unsubscribe> {
|
|
const mainWindow = windowService.getMainWindow()
|
|
if (!mainWindow) {
|
|
throw new Error('Main window is not available')
|
|
}
|
|
await this.waitForStoreReady(mainWindow.webContents)
|
|
|
|
// 在渲染进程中设置监听
|
|
await mainWindow.webContents.executeJavaScript(
|
|
`
|
|
if (!window._storeSubscriptions) {
|
|
window._storeSubscriptions = new Set();
|
|
|
|
// 设置全局状态变化监听
|
|
const unsubscribe = window.store.subscribe(() => {
|
|
const state = window.store.getState();
|
|
window.electron.ipcRenderer.send('` +
|
|
IpcChannel.ReduxStateChange +
|
|
`', state);
|
|
});
|
|
|
|
window._storeSubscriptions.add(unsubscribe);
|
|
}
|
|
`
|
|
)
|
|
|
|
// 在主进程中处理回调
|
|
const handler = async () => {
|
|
try {
|
|
const newValue = await this.select(selector)
|
|
callback(newValue)
|
|
} catch (error) {
|
|
console.error('Error in subscription handler:', error)
|
|
}
|
|
}
|
|
|
|
this.on(this.STATUS_CHANGE_EVENT, handler)
|
|
return () => {
|
|
this.off(this.STATUS_CHANGE_EVENT, handler)
|
|
}
|
|
}
|
|
|
|
// 获取整个状态树
|
|
async getState(): Promise<any> {
|
|
const mainWindow = windowService.getMainWindow()
|
|
if (!mainWindow) {
|
|
throw new Error('Main window is not available')
|
|
}
|
|
await this.waitForStoreReady(mainWindow.webContents)
|
|
try {
|
|
return await mainWindow.webContents.executeJavaScript(`
|
|
window.store.getState()
|
|
`)
|
|
} catch (error) {
|
|
console.error('Failed to get state:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// 批量执行 actions
|
|
async batch(actions: any[]): Promise<void> {
|
|
for (const action of actions) {
|
|
await this.dispatch(action)
|
|
}
|
|
}
|
|
}
|
|
|
|
export const reduxService = new ReduxService()
|
|
|
|
/** example
|
|
async function example() {
|
|
try {
|
|
// 读取状态
|
|
const settings = await reduxService.select('state.settings')
|
|
console.log('settings', settings)
|
|
|
|
// 派发 action
|
|
await reduxService.dispatch({
|
|
type: 'settings/updateApiKey',
|
|
payload: 'new-api-key'
|
|
})
|
|
|
|
// 订阅状态变化
|
|
const unsubscribe = await reduxService.subscribe('state.settings.apiKey', (newValue) => {
|
|
console.log('API key changed:', newValue)
|
|
})
|
|
|
|
// 批量执行 actions
|
|
await reduxService.batch([
|
|
{ type: 'action1', payload: 'data1' },
|
|
{ type: 'action2', payload: 'data2' }
|
|
])
|
|
|
|
// 同步方法虽然可能不是最新的数据,但响应更快
|
|
const apiKey = reduxService.selectSync('state.settings.apiKey')
|
|
console.log('apiKey', apiKey)
|
|
|
|
// 处理保证是最新的数据
|
|
const apiKey1 = await reduxService.select('state.settings.apiKey')
|
|
console.log('apiKey1', apiKey1)
|
|
|
|
// 取消订阅
|
|
unsubscribe()
|
|
} catch (error) {
|
|
console.error('Error:', error)
|
|
}
|
|
}
|
|
*/
|