feat(app-update): Refactor update handling and add manual update dialog
- Modify AppUpdater to separate update dialog logic - Add new IPC handler for manually showing update dialog - Update renderer hooks and store to track downloaded update state - Switch import for UpdateInfo from electron-updater to builder-util-runtime
This commit is contained in:
parent
b2b89a1339
commit
dcaac54c75
@ -27,7 +27,7 @@ const backupManager = new BackupManager()
|
||||
const exportService = new ExportService(fileManager)
|
||||
|
||||
export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
const { autoUpdater } = new AppUpdater(mainWindow)
|
||||
const appUpdater = new AppUpdater(mainWindow)
|
||||
|
||||
ipcMain.handle('app:info', () => ({
|
||||
version: app.getVersion(),
|
||||
@ -48,6 +48,9 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
ipcMain.handle('app:reload', () => mainWindow.reload())
|
||||
ipcMain.handle('open:website', (_, url: string) => shell.openExternal(url))
|
||||
|
||||
// Update
|
||||
ipcMain.handle('app:show-update-dialog', () => appUpdater.showUpdateDialog(mainWindow))
|
||||
|
||||
// language
|
||||
ipcMain.handle('app:set-language', (_, language) => {
|
||||
configManager.setLanguage(language)
|
||||
@ -99,9 +102,9 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
|
||||
// check for update
|
||||
ipcMain.handle('app:check-for-update', async () => {
|
||||
const update = await autoUpdater.checkForUpdates()
|
||||
const update = await appUpdater.autoUpdater.checkForUpdates()
|
||||
return {
|
||||
currentVersion: autoUpdater.currentVersion,
|
||||
currentVersion: appUpdater.autoUpdater.currentVersion,
|
||||
updateInfo: update?.updateInfo
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { UpdateInfo } from 'builder-util-runtime'
|
||||
import { app, BrowserWindow, dialog } from 'electron'
|
||||
import logger from 'electron-log'
|
||||
import { AppUpdater as _AppUpdater, autoUpdater, UpdateInfo } from 'electron-updater'
|
||||
import { AppUpdater as _AppUpdater, autoUpdater } from 'electron-updater'
|
||||
|
||||
import icon from '../../../build/icon.png?asset'
|
||||
|
||||
export default class AppUpdater {
|
||||
autoUpdater: _AppUpdater = autoUpdater
|
||||
private releaseInfo: UpdateInfo | undefined
|
||||
|
||||
constructor(mainWindow: BrowserWindow) {
|
||||
logger.transports.file.level = 'info'
|
||||
@ -37,34 +39,40 @@ export default class AppUpdater {
|
||||
|
||||
// 当需要更新的内容下载完成后
|
||||
autoUpdater.on('update-downloaded', (releaseInfo: UpdateInfo) => {
|
||||
mainWindow.webContents.send('update-downloaded')
|
||||
|
||||
logger.info('下载完成,询问用户是否更新', releaseInfo)
|
||||
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: '安装更新',
|
||||
icon,
|
||||
message: `新版本 ${releaseInfo.version} 已准备就绪`,
|
||||
detail: this.formatReleaseNotes(releaseInfo.releaseNotes),
|
||||
buttons: ['稍后安装', '立即安装'],
|
||||
defaultId: 1,
|
||||
cancelId: 0
|
||||
})
|
||||
.then(({ response }) => {
|
||||
if (response === 1) {
|
||||
app.isQuitting = true
|
||||
setImmediate(() => autoUpdater.quitAndInstall())
|
||||
} else {
|
||||
mainWindow.webContents.send('update-downloaded-cancelled')
|
||||
}
|
||||
})
|
||||
mainWindow.webContents.send('update-downloaded', releaseInfo)
|
||||
this.releaseInfo = releaseInfo
|
||||
logger.info('下载完成', releaseInfo)
|
||||
})
|
||||
|
||||
this.autoUpdater = autoUpdater
|
||||
}
|
||||
|
||||
public async showUpdateDialog(mainWindow: BrowserWindow) {
|
||||
if (!this.releaseInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: '安装更新',
|
||||
icon,
|
||||
message: `新版本 ${this.releaseInfo.version} 已准备就绪`,
|
||||
detail: this.formatReleaseNotes(this.releaseInfo.releaseNotes),
|
||||
buttons: ['稍后安装', '立即安装'],
|
||||
defaultId: 1,
|
||||
cancelId: 0
|
||||
})
|
||||
.then(({ response }) => {
|
||||
if (response === 1) {
|
||||
app.isQuitting = true
|
||||
setImmediate(() => autoUpdater.quitAndInstall())
|
||||
} else {
|
||||
mainWindow.webContents.send('update-downloaded-cancelled')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private formatReleaseNotes(releaseNotes: string | ReleaseNoteInfo[] | null | undefined): string {
|
||||
if (!releaseNotes) {
|
||||
return '暂无更新说明'
|
||||
|
||||
1
src/preload/index.d.ts
vendored
1
src/preload/index.d.ts
vendored
@ -15,6 +15,7 @@ declare global {
|
||||
api: {
|
||||
getAppInfo: () => Promise<AppInfo>
|
||||
checkForUpdate: () => Promise<{ currentVersion: string; updateInfo: UpdateInfo | null }>
|
||||
showUpdateDialog: () => Promise<void>
|
||||
openWebsite: (url: string) => void
|
||||
setProxy: (proxy: string | undefined) => void
|
||||
setLanguage: (theme: LanguageVarious) => void
|
||||
|
||||
@ -8,6 +8,7 @@ const api = {
|
||||
reload: () => ipcRenderer.invoke('app:reload'),
|
||||
setProxy: (proxy: string) => ipcRenderer.invoke('app:proxy', proxy),
|
||||
checkForUpdate: () => ipcRenderer.invoke('app:check-for-update'),
|
||||
showUpdateDialog: () => ipcRenderer.invoke('app:show-update-dialog'),
|
||||
setLanguage: (lang: string) => ipcRenderer.invoke('app:set-language', lang),
|
||||
setTray: (isActive: boolean) => ipcRenderer.invoke('app:set-tray', isActive),
|
||||
restartTray: () => ipcRenderer.invoke('app:restart-tray'),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setUpdateState } from '@renderer/store/runtime'
|
||||
import type { ProgressInfo, UpdateInfo } from 'electron-updater'
|
||||
import type { ProgressInfo, UpdateInfo } from 'builder-util-runtime'
|
||||
import { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@ -46,8 +46,14 @@ export default function useUpdateHandler() {
|
||||
})
|
||||
)
|
||||
}),
|
||||
ipcRenderer.on('update-downloaded', () => {
|
||||
dispatch(setUpdateState({ downloading: false }))
|
||||
ipcRenderer.on('update-downloaded', (_, releaseInfo: UpdateInfo) => {
|
||||
dispatch(
|
||||
setUpdateState({
|
||||
downloading: false,
|
||||
info: releaseInfo,
|
||||
downloaded: true
|
||||
})
|
||||
)
|
||||
}),
|
||||
ipcRenderer.on('update-error', (_, error) => {
|
||||
dispatch(
|
||||
|
||||
@ -68,7 +68,8 @@
|
||||
"collapse": "Collapse",
|
||||
"manage": "Manage",
|
||||
"select_model": "Select Model",
|
||||
"show.all": "Show All"
|
||||
"show.all": "Show All",
|
||||
"update_available": "Update Available"
|
||||
},
|
||||
"chat": {
|
||||
"add.assistant.title": "Add Assistant",
|
||||
|
||||
@ -68,7 +68,8 @@
|
||||
"collapse": "折りたたむ",
|
||||
"manage": "管理",
|
||||
"select_model": "モデルを選択",
|
||||
"show.all": "すべて表示"
|
||||
"show.all": "すべて表示",
|
||||
"update_available": "更新可能"
|
||||
},
|
||||
"chat": {
|
||||
"add.assistant.title": "アシスタントを追加",
|
||||
|
||||
@ -68,7 +68,8 @@
|
||||
"collapse": "Свернуть",
|
||||
"manage": "Редактировать",
|
||||
"select_model": "Выбрать модель",
|
||||
"show.all": "Показать все"
|
||||
"show.all": "Показать все",
|
||||
"update_available": "Доступно обновление"
|
||||
},
|
||||
"chat": {
|
||||
"add.assistant.title": "Добавить ассистента",
|
||||
|
||||
@ -68,7 +68,8 @@
|
||||
"collapse": "收起",
|
||||
"manage": "管理",
|
||||
"select_model": "选择模型",
|
||||
"show.all": "显示全部"
|
||||
"show.all": "显示全部",
|
||||
"update_available": "有可用更新"
|
||||
},
|
||||
"chat": {
|
||||
"add.assistant.title": "添加助手",
|
||||
|
||||
@ -68,7 +68,8 @@
|
||||
"collapse": "收起",
|
||||
"manage": "管理",
|
||||
"select_model": "選擇模型",
|
||||
"show.all": "顯示全部"
|
||||
"show.all": "顯示全部",
|
||||
"update_available": "有可用更新"
|
||||
},
|
||||
"chat": {
|
||||
"add.assistant.title": "添加助手",
|
||||
|
||||
@ -18,6 +18,7 @@ import { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import SelectModelButton from './components/SelectModelButton'
|
||||
import UpdateAppButton from './components/UpdateAppButton'
|
||||
|
||||
interface Props {
|
||||
activeAssistant: Assistant
|
||||
@ -83,6 +84,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
|
||||
<SelectModelButton assistant={assistant} />
|
||||
</HStack>
|
||||
<HStack alignItems="center" gap={8}>
|
||||
<UpdateAppButton />
|
||||
<NarrowIcon onClick={() => SearchPopup.show()}>
|
||||
<SearchOutlined />
|
||||
</NarrowIcon>
|
||||
|
||||
45
src/renderer/src/pages/home/components/UpdateAppButton.tsx
Normal file
45
src/renderer/src/pages/home/components/UpdateAppButton.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import { SyncOutlined } from '@ant-design/icons'
|
||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { Button } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const UpdateAppButton: FC = () => {
|
||||
const { update } = useRuntime()
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!update) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!update.downloaded) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<UpdateButton
|
||||
className="nodrag"
|
||||
onClick={() => window.api.showUpdateDialog()}
|
||||
icon={<SyncOutlined />}
|
||||
color="orange"
|
||||
variant="outlined"
|
||||
size="small">
|
||||
{t('button.update_available')}
|
||||
</UpdateButton>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div``
|
||||
|
||||
const UpdateButton = styled(Button)`
|
||||
border-radius: 24px;
|
||||
font-size: 12px;
|
||||
@media (max-width: 1000px) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
export default UpdateAppButton
|
||||
@ -36,6 +36,11 @@ const AboutSettings: FC = () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (update.downloaded) {
|
||||
window.api.showUpdateDialog()
|
||||
return
|
||||
}
|
||||
|
||||
dispatch(setUpdateState({ checking: true }))
|
||||
|
||||
try {
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { AppLogo, UserAvatar } from '@renderer/config/env'
|
||||
import type { UpdateInfo } from 'electron-updater'
|
||||
import type { UpdateInfo } from 'builder-util-runtime'
|
||||
|
||||
export interface UpdateState {
|
||||
info: UpdateInfo | null
|
||||
checking: boolean
|
||||
downloading: boolean
|
||||
downloaded: boolean
|
||||
downloadProgress: number
|
||||
available: boolean
|
||||
}
|
||||
@ -43,6 +44,7 @@ const initialState: RuntimeState = {
|
||||
info: null,
|
||||
checking: false,
|
||||
downloading: false,
|
||||
downloaded: false,
|
||||
downloadProgress: 0,
|
||||
available: false
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user