Compare commits
2 Commits
4501f9936f
...
00cde6e8c5
| Author | SHA1 | Date | |
|---|---|---|---|
| 00cde6e8c5 | |||
| 65cc2cd8b5 |
@ -4,6 +4,7 @@ import { Button, Flex, Modal, Progress, Typography, theme } from 'antd'
|
|||||||
|
|
||||||
import CloudIcon from '../../../Icons/CloudIcon'
|
import CloudIcon from '../../../Icons/CloudIcon'
|
||||||
import HostIcon from '../../../Icons/HostIcon'
|
import HostIcon from '../../../Icons/HostIcon'
|
||||||
|
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||||
|
|
||||||
import CheckCircleIcon from '../../../Icons/CheckCircleIcon'
|
import CheckCircleIcon from '../../../Icons/CheckCircleIcon'
|
||||||
import XMarkCircleIcon from '../../../Icons/XMarkCircleIcon'
|
import XMarkCircleIcon from '../../../Icons/XMarkCircleIcon'
|
||||||
@ -45,6 +46,15 @@ const STAGE_CONFIG = {
|
|||||||
complete: 'Installed',
|
complete: 'Installed',
|
||||||
error: 'Install failed'
|
error: 'Install failed'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
restart: {
|
||||||
|
icon: ReloadIcon,
|
||||||
|
labels: {
|
||||||
|
pending: 'Restart',
|
||||||
|
active: 'Restarting...',
|
||||||
|
complete: 'Restarted',
|
||||||
|
error: 'Restart failed'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,20 +72,25 @@ const getDownloadStageStatus = (phase, isError) => {
|
|||||||
return 'pending'
|
return 'pending'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isInstallComplete = (phase, message) =>
|
||||||
|
phase === 'installing' &&
|
||||||
|
String(message || '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes('complete')
|
||||||
|
|
||||||
const getInstallStageStatus = (phase, isError, message) => {
|
const getInstallStageStatus = (phase, isError, message) => {
|
||||||
if (isError && ['downloaded', 'installing'].includes(phase)) return 'error'
|
if (isError && ['downloaded', 'installing'].includes(phase)) return 'error'
|
||||||
if (
|
if (isInstallComplete(phase, message)) return 'complete'
|
||||||
phase === 'installing' &&
|
|
||||||
String(message || '')
|
|
||||||
.toLowerCase()
|
|
||||||
.includes('complete')
|
|
||||||
) {
|
|
||||||
return 'complete'
|
|
||||||
}
|
|
||||||
if (phase === 'installing') return 'active'
|
if (phase === 'installing') return 'active'
|
||||||
return 'pending'
|
return 'pending'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getRestartStageStatus = (phase, isError, message) => {
|
||||||
|
if (isError && isInstallComplete(phase, message)) return 'error'
|
||||||
|
if (isInstallComplete(phase, message)) return 'active'
|
||||||
|
return 'pending'
|
||||||
|
}
|
||||||
|
|
||||||
const getProgressStatus = (stageStatus) => {
|
const getProgressStatus = (stageStatus) => {
|
||||||
if (stageStatus === 'error') return 'exception'
|
if (stageStatus === 'error') return 'exception'
|
||||||
if (stageStatus === 'complete') return 'success'
|
if (stageStatus === 'complete') return 'success'
|
||||||
@ -101,25 +116,19 @@ const UpdateStage = ({ stage, status, percent, detail }) => {
|
|||||||
: StageIcon
|
: StageIcon
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex align='center' gap='middle'>
|
<Flex align='start' gap='middle' style={{ width: '100%' }}>
|
||||||
<StatusIcon style={{ fontSize: 20, color, flexShrink: 0 }} />
|
<StatusIcon style={{ fontSize: 22, color, flexShrink: 0 }} />
|
||||||
<Flex align='center' gap='small' style={{ flex: 1, minWidth: 0 }}>
|
<Flex align='start' gap='24px' style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text style={{ flexShrink: 0, minWidth: 96 }}>
|
<Text style={{ flexShrink: 0 }}>{config.labels[resolvedStatus]}</Text>
|
||||||
{config.labels[resolvedStatus]}
|
|
||||||
</Text>
|
|
||||||
{showProgress && (
|
{showProgress && (
|
||||||
<Flex vertical gap={4}>
|
<Flex vertical gap={2} style={{ flex: 1 }}>
|
||||||
<Progress
|
<Progress
|
||||||
percent={resolvedPercent}
|
percent={resolvedPercent}
|
||||||
status={getProgressStatus(resolvedStatus)}
|
status={getProgressStatus(resolvedStatus)}
|
||||||
showInfo={typeof resolvedPercent === 'number'}
|
showInfo={typeof resolvedPercent === 'number'}
|
||||||
style={{ flex: 1, margin: 0 }}
|
style={{ flex: 1, margin: 0 }}
|
||||||
/>
|
/>
|
||||||
{detail && (
|
{detail && <Text type='secondary'>{detail}</Text>}
|
||||||
<Text type='secondary' style={{ marginLeft: 36 }}>
|
|
||||||
{detail}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -128,7 +137,7 @@ const UpdateStage = ({ stage, status, percent, detail }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UpdateStage.propTypes = {
|
UpdateStage.propTypes = {
|
||||||
stage: PropTypes.oneOf(['download', 'install']).isRequired,
|
stage: PropTypes.oneOf(['download', 'install', 'restart']).isRequired,
|
||||||
status: PropTypes.oneOf(['pending', 'active', 'complete', 'error'])
|
status: PropTypes.oneOf(['pending', 'active', 'complete', 'error'])
|
||||||
.isRequired,
|
.isRequired,
|
||||||
percent: PropTypes.number,
|
percent: PropTypes.number,
|
||||||
@ -150,6 +159,7 @@ const AppUpdateProgress = ({ progress, update, onClose }) => {
|
|||||||
|
|
||||||
const downloadStatus = getDownloadStageStatus(phase, isError)
|
const downloadStatus = getDownloadStageStatus(phase, isError)
|
||||||
const installStatus = getInstallStageStatus(phase, isError, message)
|
const installStatus = getInstallStageStatus(phase, isError, message)
|
||||||
|
const restartStatus = getRestartStageStatus(phase, isError, message)
|
||||||
|
|
||||||
const downloadPercent =
|
const downloadPercent =
|
||||||
downloadStatus === 'active' ? (phase === 'preparing' ? 0 : percent) : null
|
downloadStatus === 'active' ? (phase === 'preparing' ? 0 : percent) : null
|
||||||
@ -163,6 +173,8 @@ const AppUpdateProgress = ({ progress, update, onClose }) => {
|
|||||||
|
|
||||||
const installDetail = installStatus === 'active' ? message : null
|
const installDetail = installStatus === 'active' ? message : null
|
||||||
|
|
||||||
|
const restartDetail = restartStatus === 'active' ? message : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex vertical gap='middle'>
|
<Flex vertical gap='middle'>
|
||||||
<Text>
|
<Text>
|
||||||
@ -185,6 +197,11 @@ const AppUpdateProgress = ({ progress, update, onClose }) => {
|
|||||||
percent={installPercent}
|
percent={installPercent}
|
||||||
detail={installDetail}
|
detail={installDetail}
|
||||||
/>
|
/>
|
||||||
|
<UpdateStage
|
||||||
|
stage='restart'
|
||||||
|
status={restartStatus}
|
||||||
|
detail={restartDetail}
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@ -21,6 +21,42 @@ const { Text } = Typography
|
|||||||
const UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1000
|
const UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1000
|
||||||
const DEFAULT_UPDATE_BRANCH = 'main'
|
const DEFAULT_UPDATE_BRANCH = 'main'
|
||||||
const CURRENT_BUILD_NUMBER = import.meta.env.VITE_BUILD_NUMBER
|
const CURRENT_BUILD_NUMBER = import.meta.env.VITE_BUILD_NUMBER
|
||||||
|
const APP_UPDATE_DISMISSED_KEY = 'appUpdateDismissed'
|
||||||
|
|
||||||
|
const getDismissedUpdate = () => {
|
||||||
|
try {
|
||||||
|
const stored = sessionStorage.getItem(APP_UPDATE_DISMISSED_KEY)
|
||||||
|
return stored ? JSON.parse(stored) : null
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUpdateDismissed = (update) => {
|
||||||
|
if (!update) return false
|
||||||
|
|
||||||
|
const dismissed = getDismissedUpdate()
|
||||||
|
if (!dismissed) return false
|
||||||
|
|
||||||
|
return (
|
||||||
|
dismissed.version === update.version &&
|
||||||
|
dismissed.buildNumber === update.buildNumber &&
|
||||||
|
dismissed.branch === update.branch
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveDismissedUpdate = (update) => {
|
||||||
|
if (!update) return
|
||||||
|
|
||||||
|
sessionStorage.setItem(
|
||||||
|
APP_UPDATE_DISMISSED_KEY,
|
||||||
|
JSON.stringify({
|
||||||
|
version: update.version,
|
||||||
|
buildNumber: update.buildNumber,
|
||||||
|
branch: update.branch
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const AppUpdateContext = createContext()
|
const AppUpdateContext = createContext()
|
||||||
|
|
||||||
@ -77,6 +113,7 @@ export const AppUpdateProvider = ({ children }) => {
|
|||||||
const [checking, setChecking] = useState(false)
|
const [checking, setChecking] = useState(false)
|
||||||
const [noUpdateOpen, setNoUpdateOpen] = useState(false)
|
const [noUpdateOpen, setNoUpdateOpen] = useState(false)
|
||||||
const [availableUpdate, setAvailableUpdate] = useState(null)
|
const [availableUpdate, setAvailableUpdate] = useState(null)
|
||||||
|
const [updatePromptOpen, setUpdatePromptOpen] = useState(false)
|
||||||
const [installingUpdate, setInstallingUpdate] = useState(null)
|
const [installingUpdate, setInstallingUpdate] = useState(null)
|
||||||
const [updateProgress, setUpdateProgress] = useState(null)
|
const [updateProgress, setUpdateProgress] = useState(null)
|
||||||
const runningCheckRef = useRef(null)
|
const runningCheckRef = useRef(null)
|
||||||
@ -136,14 +173,20 @@ export const AppUpdateProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const showUpdateIfAvailable = useCallback(async () => {
|
const showUpdateIfAvailable = useCallback(
|
||||||
const update = await checkForAvailableUpdate()
|
async ({ forcePrompt = false } = {}) => {
|
||||||
if (update) {
|
const update = await checkForAvailableUpdate()
|
||||||
setNoUpdateOpen(false)
|
if (update) {
|
||||||
setAvailableUpdate(update)
|
setNoUpdateOpen(false)
|
||||||
}
|
setAvailableUpdate(update)
|
||||||
return update
|
if (forcePrompt || !isUpdateDismissed(update)) {
|
||||||
}, [checkForAvailableUpdate])
|
setUpdatePromptOpen(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return update
|
||||||
|
},
|
||||||
|
[checkForAvailableUpdate]
|
||||||
|
)
|
||||||
|
|
||||||
const checkForUpdates = useCallback(async () => {
|
const checkForUpdates = useCallback(async () => {
|
||||||
if (!isElectron) return null
|
if (!isElectron) return null
|
||||||
@ -151,7 +194,7 @@ export const AppUpdateProvider = ({ children }) => {
|
|||||||
setChecking(true)
|
setChecking(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const update = await showUpdateIfAvailable()
|
const update = await showUpdateIfAvailable({ forcePrompt: true })
|
||||||
if (!update) {
|
if (!update) {
|
||||||
setNoUpdateOpen(true)
|
setNoUpdateOpen(true)
|
||||||
}
|
}
|
||||||
@ -183,15 +226,22 @@ export const AppUpdateProvider = ({ children }) => {
|
|||||||
})
|
})
|
||||||
}, [isElectron, onAppUpdateProgress])
|
}, [isElectron, onAppUpdateProgress])
|
||||||
|
|
||||||
|
const dismissUpdatePrompt = () => {
|
||||||
|
if (availableUpdate) {
|
||||||
|
saveDismissedUpdate(availableUpdate)
|
||||||
|
}
|
||||||
|
setUpdatePromptOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
const closeUpdateModal = () => {
|
const closeUpdateModal = () => {
|
||||||
setAvailableUpdate(null)
|
setUpdatePromptOpen(false)
|
||||||
setInstallingUpdate(null)
|
setInstallingUpdate(null)
|
||||||
setUpdateProgress(null)
|
setUpdateProgress(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdate = async (update) => {
|
const handleUpdate = async (update) => {
|
||||||
setNoUpdateOpen(false)
|
setNoUpdateOpen(false)
|
||||||
setAvailableUpdate(null)
|
setUpdatePromptOpen(false)
|
||||||
setInstallingUpdate(update)
|
setInstallingUpdate(update)
|
||||||
setModelWidth(550)
|
setModelWidth(550)
|
||||||
setUpdateProgress({
|
setUpdateProgress({
|
||||||
@ -215,7 +265,7 @@ export const AppUpdateProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateModalOpen = Boolean(availableUpdate || installingUpdate)
|
const updateModalOpen = Boolean(updatePromptOpen || installingUpdate)
|
||||||
const updateModalBusy =
|
const updateModalBusy =
|
||||||
Boolean(installingUpdate) && updateProgress?.phase !== 'error'
|
Boolean(installingUpdate) && updateProgress?.phase !== 'error'
|
||||||
|
|
||||||
@ -268,7 +318,13 @@ export const AppUpdateProvider = ({ children }) => {
|
|||||||
centered
|
centered
|
||||||
closable={!updateModalBusy}
|
closable={!updateModalBusy}
|
||||||
maskClosable={!updateModalBusy}
|
maskClosable={!updateModalBusy}
|
||||||
onCancel={updateModalBusy ? undefined : closeUpdateModal}
|
onCancel={
|
||||||
|
updateModalBusy
|
||||||
|
? undefined
|
||||||
|
: installingUpdate
|
||||||
|
? closeUpdateModal
|
||||||
|
: dismissUpdatePrompt
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{installingUpdate ? (
|
{installingUpdate ? (
|
||||||
<AppUpdateProgress
|
<AppUpdateProgress
|
||||||
@ -279,7 +335,7 @@ export const AppUpdateProvider = ({ children }) => {
|
|||||||
) : (
|
) : (
|
||||||
<NewAppUpdate
|
<NewAppUpdate
|
||||||
update={availableUpdate}
|
update={availableUpdate}
|
||||||
onCancel={closeUpdateModal}
|
onCancel={dismissUpdatePrompt}
|
||||||
onUpdate={handleUpdate}
|
onUpdate={handleUpdate}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user