mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 23:10:20 +08:00
feat: add updating dialog in render (#10569)
* feat: replace update dialog handling with quit and install functionality * refactor: remove App_ShowUpdateDialog and implement App_QuitAndInstall in IpcChannel * update ipc.ts to handle quit and install action * modify AppUpdater to include quitAndInstall method * adjust preload index to invoke new quit and install action * enhance AboutSettings to manage update dialog state and trigger quit and install * fix(AboutSettings): handle null update info in update dialog state management * fix(UpdateDialog): improve error handling during update installation and enhance release notes processing * fix(AppUpdater): remove redundant assignment of releaseInfo after update download * fix(IpcChannel): remove UpdateDownloadedCancelled enum value * format code * fix(UpdateDialog): enhance installation process with loading state and error handling * update i18n * fix(i18n): Auto update translations for PR #10569 * feat(UpdateAppButton): integrate UpdateDialog and update button functionality for better user experience * fix(UpdateDialog): update installation handler to support async operation and ensure modal closes after installation * refactor(AppUpdater.test): remove deprecated formatReleaseNotes tests to streamline test suite * refactor(update-dialog): simplify dialog close handling Replace onOpenChange with onClose prop to directly handle dialog closing Remove redundant handleClose function and simplify button onPress handler --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: icarus <eurfelux@gmail.com>
This commit is contained in:
parent
b6107c5fb1
commit
a2d81e6204
@ -5,8 +5,8 @@ export enum IpcChannel {
|
|||||||
App_SetLanguage = 'app:set-language',
|
App_SetLanguage = 'app:set-language',
|
||||||
App_SetEnableSpellCheck = 'app:set-enable-spell-check',
|
App_SetEnableSpellCheck = 'app:set-enable-spell-check',
|
||||||
App_SetSpellCheckLanguages = 'app:set-spell-check-languages',
|
App_SetSpellCheckLanguages = 'app:set-spell-check-languages',
|
||||||
App_ShowUpdateDialog = 'app:show-update-dialog',
|
|
||||||
App_CheckForUpdate = 'app:check-for-update',
|
App_CheckForUpdate = 'app:check-for-update',
|
||||||
|
App_QuitAndInstall = 'app:quit-and-install',
|
||||||
App_Reload = 'app:reload',
|
App_Reload = 'app:reload',
|
||||||
App_Quit = 'app:quit',
|
App_Quit = 'app:quit',
|
||||||
App_Info = 'app:info',
|
App_Info = 'app:info',
|
||||||
@ -229,7 +229,6 @@ export enum IpcChannel {
|
|||||||
// events
|
// events
|
||||||
BackupProgress = 'backup-progress',
|
BackupProgress = 'backup-progress',
|
||||||
ThemeUpdated = 'theme:updated',
|
ThemeUpdated = 'theme:updated',
|
||||||
UpdateDownloadedCancelled = 'update-downloaded-cancelled',
|
|
||||||
RestoreProgress = 'restore-progress',
|
RestoreProgress = 'restore-progress',
|
||||||
UpdateError = 'update-error',
|
UpdateError = 'update-error',
|
||||||
UpdateAvailable = 'update-available',
|
UpdateAvailable = 'update-available',
|
||||||
|
|||||||
@ -132,7 +132,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
ipcMain.handle(IpcChannel.Open_Website, (_, url: string) => shell.openExternal(url))
|
ipcMain.handle(IpcChannel.Open_Website, (_, url: string) => shell.openExternal(url))
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
ipcMain.handle(IpcChannel.App_ShowUpdateDialog, () => appUpdater.showUpdateDialog(mainWindow))
|
ipcMain.handle(IpcChannel.App_QuitAndInstall, () => appUpdater.quitAndInstall())
|
||||||
|
|
||||||
// language
|
// language
|
||||||
ipcMain.handle(IpcChannel.App_SetLanguage, (_, language) => {
|
ipcMain.handle(IpcChannel.App_SetLanguage, (_, language) => {
|
||||||
|
|||||||
@ -1,17 +1,15 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { isWin } from '@main/constant'
|
import { isWin } from '@main/constant'
|
||||||
import { getIpCountry } from '@main/utils/ipService'
|
import { getIpCountry } from '@main/utils/ipService'
|
||||||
import { locales } from '@main/utils/locales'
|
|
||||||
import { generateUserAgent } from '@main/utils/systemInfo'
|
import { generateUserAgent } from '@main/utils/systemInfo'
|
||||||
import { FeedUrl, UpgradeChannel } from '@shared/config/constant'
|
import { FeedUrl, UpgradeChannel } from '@shared/config/constant'
|
||||||
import { IpcChannel } from '@shared/IpcChannel'
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
import { CancellationToken, UpdateInfo } from 'builder-util-runtime'
|
import { CancellationToken, UpdateInfo } from 'builder-util-runtime'
|
||||||
import { app, BrowserWindow, dialog, net } from 'electron'
|
import { app, net } from 'electron'
|
||||||
import { AppUpdater as _AppUpdater, autoUpdater, Logger, NsisUpdater, UpdateCheckResult } from 'electron-updater'
|
import { AppUpdater as _AppUpdater, autoUpdater, Logger, NsisUpdater, UpdateCheckResult } from 'electron-updater'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import semver from 'semver'
|
import semver from 'semver'
|
||||||
|
|
||||||
import icon from '../../../build/icon.png?asset'
|
|
||||||
import { configManager } from './ConfigManager'
|
import { configManager } from './ConfigManager'
|
||||||
import { windowService } from './WindowService'
|
import { windowService } from './WindowService'
|
||||||
|
|
||||||
@ -26,7 +24,6 @@ const LANG_MARKERS = {
|
|||||||
|
|
||||||
export default class AppUpdater {
|
export default class AppUpdater {
|
||||||
autoUpdater: _AppUpdater = autoUpdater
|
autoUpdater: _AppUpdater = autoUpdater
|
||||||
private releaseInfo: UpdateInfo | undefined
|
|
||||||
private cancellationToken: CancellationToken = new CancellationToken()
|
private cancellationToken: CancellationToken = new CancellationToken()
|
||||||
private updateCheckResult: UpdateCheckResult | null = null
|
private updateCheckResult: UpdateCheckResult | null = null
|
||||||
|
|
||||||
@ -66,7 +63,6 @@ export default class AppUpdater {
|
|||||||
autoUpdater.on('update-downloaded', (releaseInfo: UpdateInfo) => {
|
autoUpdater.on('update-downloaded', (releaseInfo: UpdateInfo) => {
|
||||||
const processedReleaseInfo = this.processReleaseInfo(releaseInfo)
|
const processedReleaseInfo = this.processReleaseInfo(releaseInfo)
|
||||||
windowService.getMainWindow()?.webContents.send(IpcChannel.UpdateDownloaded, processedReleaseInfo)
|
windowService.getMainWindow()?.webContents.send(IpcChannel.UpdateDownloaded, processedReleaseInfo)
|
||||||
this.releaseInfo = processedReleaseInfo
|
|
||||||
logger.info('update downloaded', processedReleaseInfo)
|
logger.info('update downloaded', processedReleaseInfo)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -247,37 +243,9 @@ export default class AppUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async showUpdateDialog(mainWindow: BrowserWindow) {
|
public quitAndInstall() {
|
||||||
if (!this.releaseInfo) {
|
app.isQuitting = true
|
||||||
return
|
setImmediate(() => autoUpdater.quitAndInstall())
|
||||||
}
|
|
||||||
const locale = locales[configManager.getLanguage()]
|
|
||||||
const { update: updateLocale } = locale.translation
|
|
||||||
|
|
||||||
let detail = this.formatReleaseNotes(this.releaseInfo.releaseNotes)
|
|
||||||
if (detail === '') {
|
|
||||||
detail = updateLocale.noReleaseNotes
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog
|
|
||||||
.showMessageBox({
|
|
||||||
type: 'info',
|
|
||||||
title: updateLocale.title,
|
|
||||||
icon,
|
|
||||||
message: updateLocale.message.replace('{{version}}', this.releaseInfo.version),
|
|
||||||
detail,
|
|
||||||
buttons: [updateLocale.later, updateLocale.install],
|
|
||||||
defaultId: 1,
|
|
||||||
cancelId: 0
|
|
||||||
})
|
|
||||||
.then(({ response }) => {
|
|
||||||
if (response === 1) {
|
|
||||||
app.isQuitting = true
|
|
||||||
setImmediate(() => autoUpdater.quitAndInstall())
|
|
||||||
} else {
|
|
||||||
mainWindow.webContents.send(IpcChannel.UpdateDownloadedCancelled)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -349,38 +317,9 @@ export default class AppUpdater {
|
|||||||
|
|
||||||
return processedInfo
|
return processedInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Format release notes for display
|
|
||||||
* @param releaseNotes - Release notes in various formats
|
|
||||||
* @returns Formatted string for display
|
|
||||||
*/
|
|
||||||
private formatReleaseNotes(releaseNotes: string | ReleaseNoteInfo[] | null | undefined): string {
|
|
||||||
if (!releaseNotes) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof releaseNotes === 'string') {
|
|
||||||
// Check if it contains multi-language markers
|
|
||||||
if (this.hasMultiLanguageMarkers(releaseNotes)) {
|
|
||||||
return this.parseMultiLangReleaseNotes(releaseNotes)
|
|
||||||
}
|
|
||||||
return releaseNotes
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(releaseNotes)) {
|
|
||||||
return releaseNotes.map((note) => note.note).join('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
interface GithubReleaseInfo {
|
interface GithubReleaseInfo {
|
||||||
draft: boolean
|
draft: boolean
|
||||||
prerelease: boolean
|
prerelease: boolean
|
||||||
tag_name: string
|
tag_name: string
|
||||||
}
|
}
|
||||||
interface ReleaseNoteInfo {
|
|
||||||
readonly version: string
|
|
||||||
readonly note: string | null
|
|
||||||
}
|
|
||||||
|
|||||||
@ -274,46 +274,4 @@ describe('AppUpdater', () => {
|
|||||||
expect(result.releaseNotes).toBeNull()
|
expect(result.releaseNotes).toBeNull()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('formatReleaseNotes', () => {
|
|
||||||
it('should format string release notes with markers', () => {
|
|
||||||
vi.mocked(configManager.getLanguage).mockReturnValue('en-US')
|
|
||||||
const notes = `<!--LANG:en-->English<!--LANG:zh-CN-->中文<!--LANG:END-->`
|
|
||||||
|
|
||||||
const result = (appUpdater as any).formatReleaseNotes(notes)
|
|
||||||
|
|
||||||
expect(result).toBe('English')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should format string release notes without markers', () => {
|
|
||||||
const notes = 'Simple notes'
|
|
||||||
|
|
||||||
const result = (appUpdater as any).formatReleaseNotes(notes)
|
|
||||||
|
|
||||||
expect(result).toBe('Simple notes')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should format array release notes', () => {
|
|
||||||
const notes = [
|
|
||||||
{ version: '1.0.0', note: 'Note 1' },
|
|
||||||
{ version: '1.0.1', note: 'Note 2' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const result = (appUpdater as any).formatReleaseNotes(notes)
|
|
||||||
|
|
||||||
expect(result).toBe('Note 1\nNote 2')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle null release notes', () => {
|
|
||||||
const result = (appUpdater as any).formatReleaseNotes(null)
|
|
||||||
|
|
||||||
expect(result).toBe('')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle undefined release notes', () => {
|
|
||||||
const result = (appUpdater as any).formatReleaseNotes(undefined)
|
|
||||||
|
|
||||||
expect(result).toBe('')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -51,7 +51,7 @@ const api = {
|
|||||||
setProxy: (proxy: string | undefined, bypassRules?: string) =>
|
setProxy: (proxy: string | undefined, bypassRules?: string) =>
|
||||||
ipcRenderer.invoke(IpcChannel.App_Proxy, proxy, bypassRules),
|
ipcRenderer.invoke(IpcChannel.App_Proxy, proxy, bypassRules),
|
||||||
checkForUpdate: () => ipcRenderer.invoke(IpcChannel.App_CheckForUpdate),
|
checkForUpdate: () => ipcRenderer.invoke(IpcChannel.App_CheckForUpdate),
|
||||||
showUpdateDialog: () => ipcRenderer.invoke(IpcChannel.App_ShowUpdateDialog),
|
quitAndInstall: () => ipcRenderer.invoke(IpcChannel.App_QuitAndInstall),
|
||||||
setLanguage: (lang: string) => ipcRenderer.invoke(IpcChannel.App_SetLanguage, lang),
|
setLanguage: (lang: string) => ipcRenderer.invoke(IpcChannel.App_SetLanguage, lang),
|
||||||
setEnableSpellCheck: (isEnable: boolean) => ipcRenderer.invoke(IpcChannel.App_SetEnableSpellCheck, isEnable),
|
setEnableSpellCheck: (isEnable: boolean) => ipcRenderer.invoke(IpcChannel.App_SetEnableSpellCheck, isEnable),
|
||||||
setSpellCheckLanguages: (languages: string[]) => ipcRenderer.invoke(IpcChannel.App_SetSpellCheckLanguages, languages),
|
setSpellCheckLanguages: (languages: string[]) => ipcRenderer.invoke(IpcChannel.App_SetSpellCheckLanguages, languages),
|
||||||
|
|||||||
101
src/renderer/src/components/UpdateDialog.tsx
Normal file
101
src/renderer/src/components/UpdateDialog.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, ScrollShadow } from '@heroui/react'
|
||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { handleSaveData } from '@renderer/store'
|
||||||
|
import { ReleaseNoteInfo, UpdateInfo } from 'builder-util-runtime'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Markdown from 'react-markdown'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('UpdateDialog')
|
||||||
|
|
||||||
|
interface UpdateDialogProps {
|
||||||
|
isOpen: boolean
|
||||||
|
onClose: () => void
|
||||||
|
releaseInfo: UpdateInfo | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateDialog: React.FC<UpdateDialogProps> = ({ isOpen, onClose, releaseInfo }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [isInstalling, setIsInstalling] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && releaseInfo) {
|
||||||
|
logger.info('Update dialog opened', { version: releaseInfo.version })
|
||||||
|
}
|
||||||
|
}, [isOpen, releaseInfo])
|
||||||
|
|
||||||
|
const handleInstall = async () => {
|
||||||
|
setIsInstalling(true)
|
||||||
|
try {
|
||||||
|
await handleSaveData()
|
||||||
|
await window.api.quitAndInstall()
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to save data before update', error as Error)
|
||||||
|
setIsInstalling(false)
|
||||||
|
window.toast.error(t('update.saveDataError'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const releaseNotes = releaseInfo?.releaseNotes
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
size="2xl"
|
||||||
|
scrollBehavior="inside"
|
||||||
|
classNames={{
|
||||||
|
base: 'max-h-[85vh]',
|
||||||
|
header: 'border-b border-divider',
|
||||||
|
footer: 'border-t border-divider'
|
||||||
|
}}>
|
||||||
|
<ModalContent>
|
||||||
|
{(onModalClose) => (
|
||||||
|
<>
|
||||||
|
<ModalHeader className="flex flex-col gap-1">
|
||||||
|
<h3 className="font-semibold text-lg">{t('update.title')}</h3>
|
||||||
|
<p className="text-default-500 text-small">
|
||||||
|
{t('update.message').replace('{{version}}', releaseInfo?.version || '')}
|
||||||
|
</p>
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<ScrollShadow className="max-h-[450px]" hideScrollBar>
|
||||||
|
<div className="markdown rounded-lg bg-default-50 p-4">
|
||||||
|
<Markdown>
|
||||||
|
{typeof releaseNotes === 'string'
|
||||||
|
? releaseNotes
|
||||||
|
: Array.isArray(releaseNotes)
|
||||||
|
? releaseNotes
|
||||||
|
.map((note: ReleaseNoteInfo) => note.note)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n\n')
|
||||||
|
: t('update.noReleaseNotes')}
|
||||||
|
</Markdown>
|
||||||
|
</div>
|
||||||
|
</ScrollShadow>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant="light" onPress={onModalClose} isDisabled={isInstalling}>
|
||||||
|
{t('update.later')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onPress={async () => {
|
||||||
|
await handleInstall()
|
||||||
|
onModalClose()
|
||||||
|
}}
|
||||||
|
isLoading={isInstalling}>
|
||||||
|
{t('update.install')}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UpdateDialog
|
||||||
@ -4412,6 +4412,7 @@
|
|||||||
"later": "Later",
|
"later": "Later",
|
||||||
"message": "New version {{version}} is ready, do you want to install it now?",
|
"message": "New version {{version}} is ready, do you want to install it now?",
|
||||||
"noReleaseNotes": "No release notes",
|
"noReleaseNotes": "No release notes",
|
||||||
|
"saveDataError": "Failed to save data, please try again.",
|
||||||
"title": "Update"
|
"title": "Update"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -4412,6 +4412,7 @@
|
|||||||
"later": "稍后",
|
"later": "稍后",
|
||||||
"message": "发现新版本 {{version}},是否立即安装?",
|
"message": "发现新版本 {{version}},是否立即安装?",
|
||||||
"noReleaseNotes": "暂无更新日志",
|
"noReleaseNotes": "暂无更新日志",
|
||||||
|
"saveDataError": "保存数据失败,请重试",
|
||||||
"title": "更新提示"
|
"title": "更新提示"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -4412,6 +4412,7 @@
|
|||||||
"later": "稍後",
|
"later": "稍後",
|
||||||
"message": "新版本 {{version}} 已準備就緒,是否立即安裝?",
|
"message": "新版本 {{version}} 已準備就緒,是否立即安裝?",
|
||||||
"noReleaseNotes": "暫無更新日誌",
|
"noReleaseNotes": "暫無更新日誌",
|
||||||
|
"saveDataError": "保存數據失敗,請重試",
|
||||||
"title": "更新提示"
|
"title": "更新提示"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -4412,6 +4412,7 @@
|
|||||||
"later": "Μετά",
|
"later": "Μετά",
|
||||||
"message": "Νέα έκδοση {{version}} είναι έτοιμη, θέλετε να την εγκαταστήσετε τώρα;",
|
"message": "Νέα έκδοση {{version}} είναι έτοιμη, θέλετε να την εγκαταστήσετε τώρα;",
|
||||||
"noReleaseNotes": "Χωρίς σημειώσεις",
|
"noReleaseNotes": "Χωρίς σημειώσεις",
|
||||||
|
"saveDataError": "Η αποθήκευση των δεδομένων απέτυχε, δοκιμάστε ξανά",
|
||||||
"title": "Ενημέρωση"
|
"title": "Ενημέρωση"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -4412,6 +4412,7 @@
|
|||||||
"later": "Más tarde",
|
"later": "Más tarde",
|
||||||
"message": "Nueva versión {{version}} disponible, ¿desea instalarla ahora?",
|
"message": "Nueva versión {{version}} disponible, ¿desea instalarla ahora?",
|
||||||
"noReleaseNotes": "Sin notas de la versión",
|
"noReleaseNotes": "Sin notas de la versión",
|
||||||
|
"saveDataError": "Error al guardar los datos, inténtalo de nuevo",
|
||||||
"title": "Actualización"
|
"title": "Actualización"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -4412,6 +4412,7 @@
|
|||||||
"later": "Plus tard",
|
"later": "Plus tard",
|
||||||
"message": "Nouvelle version {{version}} disponible, voulez-vous l'installer maintenant ?",
|
"message": "Nouvelle version {{version}} disponible, voulez-vous l'installer maintenant ?",
|
||||||
"noReleaseNotes": "Aucune note de version",
|
"noReleaseNotes": "Aucune note de version",
|
||||||
|
"saveDataError": "Échec de la sauvegarde des données, veuillez réessayer",
|
||||||
"title": "Mise à jour"
|
"title": "Mise à jour"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -4412,6 +4412,7 @@
|
|||||||
"later": "後で",
|
"later": "後で",
|
||||||
"message": "新バージョン {{version}} が利用可能です。今すぐインストールしますか?",
|
"message": "新バージョン {{version}} が利用可能です。今すぐインストールしますか?",
|
||||||
"noReleaseNotes": "暫無更新日誌",
|
"noReleaseNotes": "暫無更新日誌",
|
||||||
|
"saveDataError": "データの保存に失敗しました。もう一度お試しください。",
|
||||||
"title": "更新"
|
"title": "更新"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -4412,6 +4412,7 @@
|
|||||||
"later": "Mais tarde",
|
"later": "Mais tarde",
|
||||||
"message": "Nova versão {{version}} disponível, deseja instalar agora?",
|
"message": "Nova versão {{version}} disponível, deseja instalar agora?",
|
||||||
"noReleaseNotes": "Sem notas de versão",
|
"noReleaseNotes": "Sem notas de versão",
|
||||||
|
"saveDataError": "Falha ao salvar os dados, tente novamente",
|
||||||
"title": "Atualização"
|
"title": "Atualização"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -4412,6 +4412,7 @@
|
|||||||
"later": "Позже",
|
"later": "Позже",
|
||||||
"message": "Новая версия {{version}} готова, установить сейчас?",
|
"message": "Новая версия {{version}} готова, установить сейчас?",
|
||||||
"noReleaseNotes": "Нет заметок об обновлении",
|
"noReleaseNotes": "Нет заметок об обновлении",
|
||||||
|
"saveDataError": "Ошибка сохранения данных, повторите попытку",
|
||||||
"title": "Обновление"
|
"title": "Обновление"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import { SyncOutlined } from '@ant-design/icons'
|
import { SyncOutlined } from '@ant-design/icons'
|
||||||
|
import { useDisclosure } from '@heroui/react'
|
||||||
|
import UpdateDialog from '@renderer/components/UpdateDialog'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { Button } from 'antd'
|
import { Button } from 'antd'
|
||||||
@ -10,6 +12,7 @@ const UpdateAppButton: FC = () => {
|
|||||||
const { update } = useRuntime()
|
const { update } = useRuntime()
|
||||||
const { autoCheckUpdate } = useSettings()
|
const { autoCheckUpdate } = useSettings()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||||
|
|
||||||
if (!update) {
|
if (!update) {
|
||||||
return null
|
return null
|
||||||
@ -23,13 +26,15 @@ const UpdateAppButton: FC = () => {
|
|||||||
<Container>
|
<Container>
|
||||||
<UpdateButton
|
<UpdateButton
|
||||||
className="nodrag"
|
className="nodrag"
|
||||||
onClick={() => window.api.showUpdateDialog()}
|
onClick={onOpen}
|
||||||
icon={<SyncOutlined />}
|
icon={<SyncOutlined />}
|
||||||
color="orange"
|
color="orange"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small">
|
size="small">
|
||||||
{t('button.update_available')}
|
{t('button.update_available')}
|
||||||
</UpdateButton>
|
</UpdateButton>
|
||||||
|
|
||||||
|
<UpdateDialog isOpen={isOpen} onClose={onClose} releaseInfo={update.info || null} />
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,21 @@
|
|||||||
import { GithubOutlined } from '@ant-design/icons'
|
import { GithubOutlined } from '@ant-design/icons'
|
||||||
|
import { useDisclosure } from '@heroui/react'
|
||||||
import IndicatorLight from '@renderer/components/IndicatorLight'
|
import IndicatorLight from '@renderer/components/IndicatorLight'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
|
import UpdateDialog from '@renderer/components/UpdateDialog'
|
||||||
import { APP_NAME, AppLogo } from '@renderer/config/env'
|
import { APP_NAME, AppLogo } from '@renderer/config/env'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import { handleSaveData, useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setUpdateState } from '@renderer/store/runtime'
|
import { setUpdateState } from '@renderer/store/runtime'
|
||||||
import { ThemeMode } from '@renderer/types'
|
import { ThemeMode } from '@renderer/types'
|
||||||
import { runAsyncFunction } from '@renderer/utils'
|
import { runAsyncFunction } from '@renderer/utils'
|
||||||
import { UpgradeChannel } from '@shared/config/constant'
|
import { UpgradeChannel } from '@shared/config/constant'
|
||||||
import { Avatar, Button, Progress, Radio, Row, Switch, Tag, Tooltip } from 'antd'
|
import { Avatar, Button, Progress, Radio, Row, Switch, Tag, Tooltip } from 'antd'
|
||||||
|
import { UpdateInfo } from 'builder-util-runtime'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import { Bug, FileCheck, Globe, Mail, Rss } from 'lucide-react'
|
import { Bug, FileCheck, Globe, Mail, Rss } from 'lucide-react'
|
||||||
import { BadgeQuestionMark } from 'lucide-react'
|
import { BadgeQuestionMark } from 'lucide-react'
|
||||||
@ -27,6 +30,8 @@ import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingTitl
|
|||||||
const AboutSettings: FC = () => {
|
const AboutSettings: FC = () => {
|
||||||
const [version, setVersion] = useState('')
|
const [version, setVersion] = useState('')
|
||||||
const [isPortable, setIsPortable] = useState(false)
|
const [isPortable, setIsPortable] = useState(false)
|
||||||
|
const [updateDialogInfo, setUpdateDialogInfo] = useState<UpdateInfo | null>(null)
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { autoCheckUpdate, setAutoCheckUpdate, testPlan, setTestPlan, testChannel, setTestChannel } = useSettings()
|
const { autoCheckUpdate, setAutoCheckUpdate, testPlan, setTestPlan, testChannel, setTestChannel } = useSettings()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
@ -41,8 +46,9 @@ const AboutSettings: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (update.downloaded) {
|
if (update.downloaded) {
|
||||||
await handleSaveData()
|
// Open update dialog directly in renderer
|
||||||
window.api.showUpdateDialog()
|
setUpdateDialogInfo(update.info || null)
|
||||||
|
onOpen()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,6 +347,9 @@ const AboutSettings: FC = () => {
|
|||||||
<Button onClick={debug}>{t('settings.about.debug.open')}</Button>
|
<Button onClick={debug}>{t('settings.about.debug.open')}</Button>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
|
|
||||||
|
{/* Update Dialog */}
|
||||||
|
<UpdateDialog isOpen={isOpen} onClose={onClose} releaseInfo={updateDialogInfo} />
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user