feat: add update info ui
This commit is contained in:
parent
aeff59946c
commit
b5a109401c
@ -1,6 +1,8 @@
|
|||||||
# provider: generic
|
# provider: generic
|
||||||
# url: http://127.0.0.1:8080
|
# url: http://127.0.0.1:8080
|
||||||
# updaterCacheDirName: cherry-studio-updater
|
# updaterCacheDirName: cherry-studio-updater
|
||||||
provider: github
|
# provider: github
|
||||||
repo: cherry-studio
|
# repo: cherry-studio
|
||||||
owner: kangfenmao
|
# owner: kangfenmao
|
||||||
|
provider: generic
|
||||||
|
url: https://cherrystudio.ocool.online
|
||||||
|
|||||||
@ -55,9 +55,8 @@ appImage:
|
|||||||
artifactName: ${productName}-${version}-${arch}.${ext}
|
artifactName: ${productName}-${version}-${arch}.${ext}
|
||||||
npmRebuild: false
|
npmRebuild: false
|
||||||
publish:
|
publish:
|
||||||
provider: github
|
provider: generic
|
||||||
repo: cherry-studio
|
url: https://cherrystudio.ocool.online
|
||||||
owner: kangfenmao
|
|
||||||
electronDownload:
|
electronDownload:
|
||||||
mirror: https://npmmirror.com/mirrors/electron/
|
mirror: https://npmmirror.com/mirrors/electron/
|
||||||
afterSign: scripts/notarize.js
|
afterSign: scripts/notarize.js
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
exclude: []
|
exclude: ['chunk-7UIZINC5.js', 'chunk-7OJJKI46.js']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { app, BrowserWindow, dialog } from 'electron'
|
|||||||
import logger from 'electron-log'
|
import logger from 'electron-log'
|
||||||
import { AppUpdater as _AppUpdater, autoUpdater, UpdateInfo } from 'electron-updater'
|
import { AppUpdater as _AppUpdater, autoUpdater, UpdateInfo } from 'electron-updater'
|
||||||
|
|
||||||
|
import icon from '../../../build/icon.png?asset'
|
||||||
|
|
||||||
export default class AppUpdater {
|
export default class AppUpdater {
|
||||||
autoUpdater: _AppUpdater = autoUpdater
|
autoUpdater: _AppUpdater = autoUpdater
|
||||||
|
|
||||||
@ -43,6 +45,7 @@ export default class AppUpdater {
|
|||||||
.showMessageBox({
|
.showMessageBox({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: '安装更新',
|
title: '安装更新',
|
||||||
|
icon,
|
||||||
message: `新版本 ${releaseInfo.version} 已准备就绪`,
|
message: `新版本 ${releaseInfo.version} 已准备就绪`,
|
||||||
detail: this.formatReleaseNotes(releaseInfo.releaseNotes),
|
detail: this.formatReleaseNotes(releaseInfo.releaseNotes),
|
||||||
buttons: ['稍后安装', '立即安装'],
|
buttons: ['稍后安装', '立即安装'],
|
||||||
|
|||||||
3
src/preload/index.d.ts
vendored
3
src/preload/index.d.ts
vendored
@ -3,6 +3,7 @@ import { FileType } from '@renderer/types'
|
|||||||
import { WebDavConfig } from '@renderer/types'
|
import { WebDavConfig } from '@renderer/types'
|
||||||
import { AppInfo, LanguageVarious } from '@renderer/types'
|
import { AppInfo, LanguageVarious } from '@renderer/types'
|
||||||
import type { OpenDialogOptions } from 'electron'
|
import type { OpenDialogOptions } from 'electron'
|
||||||
|
import type { UpdateInfo } from 'electron-updater'
|
||||||
import { Readable } from 'stream'
|
import { Readable } from 'stream'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@ -10,7 +11,7 @@ declare global {
|
|||||||
electron: ElectronAPI
|
electron: ElectronAPI
|
||||||
api: {
|
api: {
|
||||||
getAppInfo: () => Promise<AppInfo>
|
getAppInfo: () => Promise<AppInfo>
|
||||||
checkForUpdate: () => void
|
checkForUpdate: () => Promise<{ currentVersion: string; updateInfo: UpdateInfo | null }>
|
||||||
openWebsite: (url: string) => void
|
openWebsite: (url: string) => void
|
||||||
setProxy: (proxy: string | undefined) => void
|
setProxy: (proxy: string | undefined) => void
|
||||||
setLanguage: (theme: LanguageVarious) => void
|
setLanguage: (theme: LanguageVarious) => void
|
||||||
|
|||||||
35
src/renderer/src/components/IndicatorLight.tsx
Normal file
35
src/renderer/src/components/IndicatorLight.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// src/renderer/src/components/IndicatorLight.tsx
|
||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface IndicatorLightProps {
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Light = styled.div<{ color: string }>`
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: ${({ color }) => color};
|
||||||
|
box-shadow: 0 0 6px ${({ color }) => color};
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const IndicatorLight: React.FC<IndicatorLightProps> = ({ color }) => {
|
||||||
|
const actualColor = color === 'green' ? '#22c55e' : color
|
||||||
|
return <Light color={actualColor} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IndicatorLight
|
||||||
@ -3,14 +3,15 @@ import { isLocalAi } from '@renderer/config/env'
|
|||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setAvatar, setFilesPath } from '@renderer/store/runtime'
|
import { setAvatar, setFilesPath, setUpdateState } from '@renderer/store/runtime'
|
||||||
import { runAsyncFunction } from '@renderer/utils'
|
import { delay, runAsyncFunction } from '@renderer/utils'
|
||||||
import { useLiveQuery } from 'dexie-react-hooks'
|
import { useLiveQuery } from 'dexie-react-hooks'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
import { useDefaultModel } from './useAssistant'
|
import { useDefaultModel } from './useAssistant'
|
||||||
import { useRuntime } from './useRuntime'
|
import { useRuntime } from './useRuntime'
|
||||||
import { useSettings } from './useSettings'
|
import { useSettings } from './useSettings'
|
||||||
|
import useUpdateHandler from './useUpdateHandler'
|
||||||
|
|
||||||
export function useAppInit() {
|
export function useAppInit() {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
@ -19,6 +20,8 @@ export function useAppInit() {
|
|||||||
const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel()
|
const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel()
|
||||||
const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
|
const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
|
||||||
|
|
||||||
|
useUpdateHandler()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
avatar?.value && dispatch(setAvatar(avatar.value))
|
avatar?.value && dispatch(setAvatar(avatar.value))
|
||||||
}, [avatar, dispatch])
|
}, [avatar, dispatch])
|
||||||
@ -28,11 +31,12 @@ export function useAppInit() {
|
|||||||
runAsyncFunction(async () => {
|
runAsyncFunction(async () => {
|
||||||
const { isPackaged } = await window.api.getAppInfo()
|
const { isPackaged } = await window.api.getAppInfo()
|
||||||
if (isPackaged && !manualUpdateCheck) {
|
if (isPackaged && !manualUpdateCheck) {
|
||||||
setTimeout(window.api.checkForUpdate, 3000)
|
await delay(2)
|
||||||
|
const { updateInfo } = await window.api.checkForUpdate()
|
||||||
|
dispatch(setUpdateState({ info: updateInfo }))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [dispatch, manualUpdateCheck])
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (proxyMode === 'system') {
|
if (proxyMode === 'system') {
|
||||||
|
|||||||
64
src/renderer/src/hooks/useUpdateHandler.ts
Normal file
64
src/renderer/src/hooks/useUpdateHandler.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { useAppDispatch } from '@renderer/store'
|
||||||
|
import { setUpdateState } from '@renderer/store/runtime'
|
||||||
|
import type { ProgressInfo, UpdateInfo } from 'electron-updater'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
export default function useUpdateHandler() {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ipcRenderer = window.electron.ipcRenderer
|
||||||
|
const removers = [
|
||||||
|
ipcRenderer.on('update-not-available', () => {
|
||||||
|
dispatch(setUpdateState({ checking: false }))
|
||||||
|
window.message.success(t('settings.about.updateNotAvailable'))
|
||||||
|
}),
|
||||||
|
ipcRenderer.on('update-available', (_, releaseInfo: UpdateInfo) => {
|
||||||
|
dispatch(
|
||||||
|
setUpdateState({
|
||||||
|
checking: false,
|
||||||
|
downloading: true,
|
||||||
|
info: releaseInfo,
|
||||||
|
available: true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
ipcRenderer.on('download-update', () => {
|
||||||
|
dispatch(
|
||||||
|
setUpdateState({
|
||||||
|
checking: false,
|
||||||
|
downloading: true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
ipcRenderer.on('download-progress', (_, progress: ProgressInfo) => {
|
||||||
|
dispatch(
|
||||||
|
setUpdateState({
|
||||||
|
downloading: progress.percent < 100,
|
||||||
|
downloadProgress: progress.percent
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
ipcRenderer.on('update-downloaded', () => {
|
||||||
|
dispatch(setUpdateState({ downloading: false }))
|
||||||
|
}),
|
||||||
|
ipcRenderer.on('update-error', (_, error) => {
|
||||||
|
dispatch(
|
||||||
|
setUpdateState({
|
||||||
|
checking: false,
|
||||||
|
downloading: false,
|
||||||
|
downloadProgress: 0
|
||||||
|
})
|
||||||
|
)
|
||||||
|
window.modal.info({
|
||||||
|
title: t('settings.about.updateError'),
|
||||||
|
content: error?.message || t('settings.about.updateError'),
|
||||||
|
icon: null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]
|
||||||
|
return () => removers.forEach((remover) => remover())
|
||||||
|
}, [dispatch, t])
|
||||||
|
}
|
||||||
@ -316,6 +316,7 @@
|
|||||||
"settings": {
|
"settings": {
|
||||||
"about": "About & Feedback",
|
"about": "About & Feedback",
|
||||||
"about.checkUpdate": "Check Update",
|
"about.checkUpdate": "Check Update",
|
||||||
|
"about.checkUpdate.available": "Update",
|
||||||
"about.checkingUpdate": "Checking for updates...",
|
"about.checkingUpdate": "Checking for updates...",
|
||||||
"about.contact.button": "Email",
|
"about.contact.button": "Email",
|
||||||
"about.contact.title": "Contact",
|
"about.contact.title": "Contact",
|
||||||
|
|||||||
@ -316,6 +316,7 @@
|
|||||||
"settings": {
|
"settings": {
|
||||||
"about": "О программе и обратная связь",
|
"about": "О программе и обратная связь",
|
||||||
"about.checkUpdate": "Проверить обновления",
|
"about.checkUpdate": "Проверить обновления",
|
||||||
|
"about.checkUpdate.available": "Обновить",
|
||||||
"about.checkingUpdate": "Проверка обновлений...",
|
"about.checkingUpdate": "Проверка обновлений...",
|
||||||
"about.contact.button": "Электронная почта",
|
"about.contact.button": "Электронная почта",
|
||||||
"about.contact.title": "Контакты",
|
"about.contact.title": "Контакты",
|
||||||
|
|||||||
@ -316,6 +316,7 @@
|
|||||||
"settings": {
|
"settings": {
|
||||||
"about": "关于我们",
|
"about": "关于我们",
|
||||||
"about.checkUpdate": "检查更新",
|
"about.checkUpdate": "检查更新",
|
||||||
|
"about.checkUpdate.available": "立即更新",
|
||||||
"about.checkingUpdate": "正在检查更新...",
|
"about.checkingUpdate": "正在检查更新...",
|
||||||
"about.contact.button": "邮件",
|
"about.contact.button": "邮件",
|
||||||
"about.contact.title": "邮件联系",
|
"about.contact.title": "邮件联系",
|
||||||
|
|||||||
@ -316,6 +316,7 @@
|
|||||||
"settings": {
|
"settings": {
|
||||||
"about": "關於與回饋",
|
"about": "關於與回饋",
|
||||||
"about.checkUpdate": "檢查更新",
|
"about.checkUpdate": "檢查更新",
|
||||||
|
"about.checkUpdate.available": "立即更新",
|
||||||
"about.checkingUpdate": "正在檢查更新...",
|
"about.checkingUpdate": "正在檢查更新...",
|
||||||
"about.contact.button": "郵件",
|
"about.contact.button": "郵件",
|
||||||
"about.contact.title": "聯繫方式",
|
"about.contact.title": "聯繫方式",
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
import { GithubOutlined } from '@ant-design/icons'
|
import { GithubOutlined } from '@ant-design/icons'
|
||||||
import { FileProtectOutlined, GlobalOutlined, MailOutlined, SoundOutlined } from '@ant-design/icons'
|
import { FileProtectOutlined, GlobalOutlined, MailOutlined, SoundOutlined } from '@ant-design/icons'
|
||||||
|
import IndicatorLight from '@renderer/components/IndicatorLight'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import MinApp from '@renderer/components/MinApp'
|
import MinApp from '@renderer/components/MinApp'
|
||||||
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 { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
|
import { setUpdateState } from '@renderer/store/runtime'
|
||||||
import { setManualUpdateCheck } from '@renderer/store/settings'
|
import { setManualUpdateCheck } from '@renderer/store/settings'
|
||||||
import { runAsyncFunction } from '@renderer/utils'
|
import { runAsyncFunction } from '@renderer/utils'
|
||||||
import { Avatar, Button, Progress, Row, Switch, Tag } from 'antd'
|
import { Avatar, Button, Progress, Row, Switch, Tag } from 'antd'
|
||||||
import { ProgressInfo, UpdateInfo } from 'electron-updater'
|
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -22,17 +24,18 @@ import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingTitl
|
|||||||
const AboutSettings: FC = () => {
|
const AboutSettings: FC = () => {
|
||||||
const [version, setVersion] = useState('')
|
const [version, setVersion] = useState('')
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [percent, setPercent] = useState(0)
|
|
||||||
const [checkUpdateLoading, setCheckUpdateLoading] = useState(false)
|
|
||||||
const [downloading, setDownloading] = useState(false)
|
|
||||||
const { manualUpdateCheck } = useSettings()
|
const { manualUpdateCheck } = useSettings()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
const { update } = useRuntime()
|
||||||
|
|
||||||
const onCheckUpdate = debounce(
|
const onCheckUpdate = debounce(
|
||||||
async () => {
|
async () => {
|
||||||
if (checkUpdateLoading || downloading) return
|
if (update.checking || update.downloading) {
|
||||||
setCheckUpdateLoading(true)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(setUpdateState({ checking: true }))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.api.checkForUpdate()
|
await window.api.checkForUpdate()
|
||||||
@ -40,7 +43,7 @@ const AboutSettings: FC = () => {
|
|||||||
window.message.error(t('settings.about.updateError'))
|
window.message.error(t('settings.about.updateError'))
|
||||||
}
|
}
|
||||||
|
|
||||||
setCheckUpdateLoading(false)
|
dispatch(setUpdateState({ checking: false }))
|
||||||
},
|
},
|
||||||
2000,
|
2000,
|
||||||
{ leading: true, trailing: false }
|
{ leading: true, trailing: false }
|
||||||
@ -75,52 +78,6 @@ const AboutSettings: FC = () => {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const ipcRenderer = window.electron.ipcRenderer
|
|
||||||
const removers = [
|
|
||||||
ipcRenderer.on('update-not-available', () => {
|
|
||||||
setCheckUpdateLoading(false)
|
|
||||||
window.message.success(t('settings.about.updateNotAvailable'))
|
|
||||||
}),
|
|
||||||
ipcRenderer.on('update-available', (_, releaseInfo: UpdateInfo) => {
|
|
||||||
setCheckUpdateLoading(false)
|
|
||||||
setDownloading(true)
|
|
||||||
window.modal.info({
|
|
||||||
title: t('settings.about.updateAvailable', { version: releaseInfo.version }),
|
|
||||||
content: (
|
|
||||||
<Markdown>
|
|
||||||
{typeof releaseInfo.releaseNotes === 'string'
|
|
||||||
? releaseInfo.releaseNotes
|
|
||||||
: releaseInfo.releaseNotes?.map((note) => note.note).join('\n')}
|
|
||||||
</Markdown>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
ipcRenderer.on('download-update', () => {
|
|
||||||
setCheckUpdateLoading(false)
|
|
||||||
setDownloading(true)
|
|
||||||
}),
|
|
||||||
ipcRenderer.on('download-progress', (_, progress: ProgressInfo) => {
|
|
||||||
setPercent(progress.percent)
|
|
||||||
setDownloading(progress.percent < 100)
|
|
||||||
}),
|
|
||||||
ipcRenderer.on('update-downloaded', () => {
|
|
||||||
setDownloading(false)
|
|
||||||
}),
|
|
||||||
ipcRenderer.on('update-error', (_, error) => {
|
|
||||||
setCheckUpdateLoading(false)
|
|
||||||
setDownloading(false)
|
|
||||||
setPercent(0)
|
|
||||||
window.modal.info({
|
|
||||||
title: t('settings.about.updateError'),
|
|
||||||
content: error?.message || t('settings.about.updateError'),
|
|
||||||
icon: null
|
|
||||||
})
|
|
||||||
})
|
|
||||||
]
|
|
||||||
return () => removers.forEach((remover) => remover())
|
|
||||||
}, [t])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer theme={theme}>
|
<SettingContainer theme={theme}>
|
||||||
<SettingGroup theme={theme}>
|
<SettingGroup theme={theme}>
|
||||||
@ -136,11 +93,11 @@ const AboutSettings: FC = () => {
|
|||||||
<AboutHeader>
|
<AboutHeader>
|
||||||
<Row align="middle">
|
<Row align="middle">
|
||||||
<AvatarWrapper onClick={() => onOpenWebsite('https://github.com/kangfenmao/cherry-studio')}>
|
<AvatarWrapper onClick={() => onOpenWebsite('https://github.com/kangfenmao/cherry-studio')}>
|
||||||
{percent > 0 && (
|
{update.downloadProgress > 0 && (
|
||||||
<ProgressCircle
|
<ProgressCircle
|
||||||
type="circle"
|
type="circle"
|
||||||
size={84}
|
size={84}
|
||||||
percent={percent}
|
percent={update.downloadProgress}
|
||||||
showInfo={false}
|
showInfo={false}
|
||||||
strokeLinecap="butt"
|
strokeLinecap="butt"
|
||||||
strokeColor="#67ad5b"
|
strokeColor="#67ad5b"
|
||||||
@ -161,9 +118,13 @@ const AboutSettings: FC = () => {
|
|||||||
</Row>
|
</Row>
|
||||||
<CheckUpdateButton
|
<CheckUpdateButton
|
||||||
onClick={onCheckUpdate}
|
onClick={onCheckUpdate}
|
||||||
loading={checkUpdateLoading}
|
loading={update.checking}
|
||||||
disabled={downloading || checkUpdateLoading}>
|
disabled={update.downloading || update.checking}>
|
||||||
{downloading ? t('settings.about.downloading') : t('settings.about.checkUpdate')}
|
{update.downloading
|
||||||
|
? t('settings.about.downloading')
|
||||||
|
: update.available
|
||||||
|
? t('settings.about.checkUpdate.available')
|
||||||
|
: t('settings.about.checkUpdate')}
|
||||||
</CheckUpdateButton>
|
</CheckUpdateButton>
|
||||||
</AboutHeader>
|
</AboutHeader>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
@ -172,6 +133,23 @@ const AboutSettings: FC = () => {
|
|||||||
<Switch value={manualUpdateCheck} onChange={(v) => dispatch(setManualUpdateCheck(v))} />
|
<Switch value={manualUpdateCheck} onChange={(v) => dispatch(setManualUpdateCheck(v))} />
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
|
{update.info && (
|
||||||
|
<SettingGroup theme={theme}>
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitle>
|
||||||
|
{t('settings.about.updateAvailable', { version: update.info.version })}
|
||||||
|
<IndicatorLight color="green" />
|
||||||
|
</SettingRowTitle>
|
||||||
|
</SettingRow>
|
||||||
|
<UpdateNotesWrapper>
|
||||||
|
<Markdown>
|
||||||
|
{typeof update.info.releaseNotes === 'string'
|
||||||
|
? update.info.releaseNotes.replaceAll('\n', '\n\n')
|
||||||
|
: update.info.releaseNotes?.map((note) => note.note).join('\n')}
|
||||||
|
</Markdown>
|
||||||
|
</UpdateNotesWrapper>
|
||||||
|
</SettingGroup>
|
||||||
|
)}
|
||||||
<SettingGroup theme={theme}>
|
<SettingGroup theme={theme}>
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitle>
|
<SettingRowTitle>
|
||||||
@ -285,4 +263,17 @@ export const SettingRowTitle = styled.div`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const UpdateNotesWrapper = styled.div`
|
||||||
|
padding: 12px 0;
|
||||||
|
margin: 8px 0;
|
||||||
|
background-color: var(--color-bg-2);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export default AboutSettings
|
export default AboutSettings
|
||||||
|
|||||||
@ -1,5 +1,14 @@
|
|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||||
import { AppLogo, UserAvatar } from '@renderer/config/env'
|
import { AppLogo, UserAvatar } from '@renderer/config/env'
|
||||||
|
import type { UpdateInfo } from 'electron-updater'
|
||||||
|
|
||||||
|
export interface UpdateState {
|
||||||
|
info: UpdateInfo | null
|
||||||
|
checking: boolean
|
||||||
|
downloading: boolean
|
||||||
|
downloadProgress: number
|
||||||
|
available: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface RuntimeState {
|
export interface RuntimeState {
|
||||||
avatar: string
|
avatar: string
|
||||||
@ -7,6 +16,7 @@ export interface RuntimeState {
|
|||||||
minappShow: boolean
|
minappShow: boolean
|
||||||
searching: boolean
|
searching: boolean
|
||||||
filesPath: string
|
filesPath: string
|
||||||
|
update: UpdateState
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: RuntimeState = {
|
const initialState: RuntimeState = {
|
||||||
@ -14,7 +24,14 @@ const initialState: RuntimeState = {
|
|||||||
generating: false,
|
generating: false,
|
||||||
minappShow: false,
|
minappShow: false,
|
||||||
searching: false,
|
searching: false,
|
||||||
filesPath: ''
|
filesPath: '',
|
||||||
|
update: {
|
||||||
|
info: null,
|
||||||
|
checking: false,
|
||||||
|
downloading: false,
|
||||||
|
downloadProgress: 0,
|
||||||
|
available: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const runtimeSlice = createSlice({
|
const runtimeSlice = createSlice({
|
||||||
@ -35,10 +52,14 @@ const runtimeSlice = createSlice({
|
|||||||
},
|
},
|
||||||
setFilesPath: (state, action: PayloadAction<string>) => {
|
setFilesPath: (state, action: PayloadAction<string>) => {
|
||||||
state.filesPath = action.payload
|
state.filesPath = action.payload
|
||||||
|
},
|
||||||
|
setUpdateState: (state, action: PayloadAction<Partial<UpdateState>>) => {
|
||||||
|
state.update = { ...state.update, ...action.payload }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const { setAvatar, setGenerating, setMinappShow, setSearching, setFilesPath } = runtimeSlice.actions
|
export const { setAvatar, setGenerating, setMinappShow, setSearching, setFilesPath, setUpdateState } =
|
||||||
|
runtimeSlice.actions
|
||||||
|
|
||||||
export default runtimeSlice.reducer
|
export default runtimeSlice.reducer
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user