Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:Detect new upgrade on launch #2783

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ $action-button-width: 11.25rem;
background: var(--input-disabled-color);
border-radius: 8px;
margin-right: 16px;

p {
font-size: 14px;
line-height: 20px;
Expand All @@ -33,6 +34,25 @@ $action-button-width: 11.25rem;
}
}
}
.showVersion {
position: relative;

&::after {
content: attr(data-new-version-tip);
background-color: #ff1e1e;
border-top-right-radius: 8px;
border-bottom-left-radius: 8px;
position: absolute;
top: 0;
right: 0;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: normal;
padding: 2px 4px;
color: #fff;
}
}
}

.install {
Expand Down
46 changes: 46 additions & 0 deletions packages/neuron-ui/src/components/GeneralSetting/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Dispatch, SetStateAction, useCallback, useMemo, useState } from 'react'
import { cancelCheckUpdates, cancelDownloadUpdate, checkForUpdates } from 'services/remote'

export const useUpdateDownloadStatus = ({
setShowCheckDialog,
downloadProgress,
}: {
setShowCheckDialog: Dispatch<SetStateAction<boolean>>
downloadProgress: number
}) => {
const [showUpdateDownloadStatus, setShowUpdateDownloadStatus] = useState(false)
const openShowUpdateDownloadStatus = useCallback(() => {
setShowUpdateDownloadStatus(true)
}, [])
const onCheckUpdate = useCallback(() => {
setShowCheckDialog(true)
checkForUpdates()
}, [setShowCheckDialog])
const hasStartDownload = useMemo(() => downloadProgress >= 0, [downloadProgress])
return {
showUpdateDownloadStatus,
openShowUpdateDownloadStatus,
onCheckUpdate,
onCancel: useCallback(() => {
if (hasStartDownload) {
cancelDownloadUpdate()
}
setShowUpdateDownloadStatus(false)
}, [hasStartDownload]),
}
}

export const useCheckUpdate = () => {
const [showCheckDialog, setShowCheckDialog] = useState(false)
const onCancelCheckUpdates = useCallback(() => {
if (showCheckDialog) {
cancelCheckUpdates()
}
setShowCheckDialog(false)
}, [showCheckDialog])
return {
showCheckDialog,
setShowCheckDialog,
onCancelCheckUpdates,
}
}
98 changes: 35 additions & 63 deletions packages/neuron-ui/src/components/GeneralSetting/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,12 @@ import LanguageDialog from 'components/LanguageDialog'
import AlertDialog from 'widgets/AlertDialog'
import { ReactComponent as VersionLogo } from 'widgets/Icons/VersionLogo.svg'
import { ReactComponent as ArrowNext } from 'widgets/Icons/ArrowNext.svg'
import {
checkForUpdates,
cancelCheckUpdates,
downloadUpdate,
cancelDownloadUpdate,
installUpdate,
getVersion,
} from 'services/remote'
import { uniformTimeFormatter, bytesFormatter } from 'utils'
import { ReactComponent as Update } from 'widgets/Icons/Update.svg'
import { cancelCheckUpdates, downloadUpdate, installUpdate, getVersion } from 'services/remote'
import { uniformTimeFormatter, bytesFormatter, clsx } from 'utils'
import { LanguageSelect } from 'widgets/Icons/icon'
import styles from './generalSetting.module.scss'
import { useCheckUpdate, useUpdateDownloadStatus } from './hooks'

interface UpdateDownloadStatusProps {
show: boolean
Expand Down Expand Up @@ -135,66 +130,47 @@ const GeneralSetting = ({ updater }: GeneralSettingProps) => {
const [showLangDialog, setShowLangDialog] = useState(false)
const [searchParams] = useSearchParams()
const [errorMsg, setErrorMsg] = useState('')
const [dialogType, setDialogType] = useState<'' | 'checking' | 'updating' | 'updated'>('')
const { showCheckDialog, setShowCheckDialog, onCancelCheckUpdates } = useCheckUpdate()
const { version: newVersion, checking, downloadProgress } = updater
const { showUpdateDownloadStatus, openShowUpdateDownloadStatus, onCheckUpdate, onCancel } = useUpdateDownloadStatus({
setShowCheckDialog,
downloadProgress,
})

const version = useMemo(() => {
useEffect(() => {
if (showCheckDialog && newVersion) {
setShowCheckDialog(false)
openShowUpdateDownloadStatus()
}
}, [showCheckDialog, newVersion, openShowUpdateDownloadStatus, setShowCheckDialog])

const currentVersion = useMemo(() => {
return getVersion()
}, [])

useEffect(() => {
const checkUpdate = searchParams.get('checkUpdate')
if (checkUpdate === '1') {
checkForUpdates()
onCheckUpdate()
}
}, [searchParams, checkForUpdates])
}, [searchParams, onCheckUpdate])

useEffect(() => {
if (updater.errorMsg) {
setErrorMsg(updater.errorMsg)
cancelCheckUpdates()
return
}
if (updater.isUpdated) {
setDialogType('updated')
return
}
if (updater.checking) {
setDialogType('checking')
return
}
if (updater.version || updater.downloadProgress > 0) {
setDialogType('updating')
return
}
setDialogType('')
}, [updater, setDialogType, setErrorMsg])

const handleUpdate = useCallback(
(e: React.SyntheticEvent) => {
const {
dataset: { method },
} = e.target as HTMLElement

if (method === 'cancelCheck') {
if (dialogType === 'checking') {
cancelCheckUpdates()
}
setDialogType('')
} else if (method === 'check') {
checkForUpdates()
}
},
[dialogType, setDialogType, cancelCheckUpdates, checkForUpdates]
)
}, [updater.errorMsg, setErrorMsg])

return (
<div className={styles.container}>
<div className={styles.content}>
<div className={clsx(styles.content, `${newVersion ? styles.showVersion : ''}`)} data-new-version-tip="New">
<p>
{t('settings.general.version')} v{version}
{t('settings.general.version')} v{newVersion || currentVersion}
</p>
<button type="button" onClick={handleUpdate} data-method="check">
{t(`updates.check-updates`)} <ArrowNext />
<button type="button" onClick={newVersion ? openShowUpdateDownloadStatus : onCheckUpdate} data-method="check">
<Update />
{t(newVersion ? 'updates.install-update' : 'updates.check-updates')} <ArrowNext />
</button>
</div>

Expand All @@ -216,32 +192,28 @@ const GeneralSetting = ({ updater }: GeneralSettingProps) => {
title={t(`updates.check-updates`)}
message={errorMsg}
type="failed"
onCancel={() => setErrorMsg('')}
onCancel={() => {
setErrorMsg('')
}}
/>

<Dialog
show={['checking', 'updated'].includes(dialogType)}
show={showCheckDialog}
showCancel={false}
showHeader={false}
confirmText={t(dialogType === 'checking' ? 'common.cancel' : 'common.ok')}
onConfirm={handleUpdate}
confirmText={t(checking ? 'common.cancel' : 'common.ok')}
onConfirm={onCancelCheckUpdates}
className={styles.confirmDialog}
confirmProps={{
'data-method': 'cancelCheck',
}}
>
<div className={styles.wrap}>
<VersionLogo />
<p>{t(dialogType === 'checking' ? 'updates.checking-updates' : 'updates.update-not-available')}</p>
<p>{t(checking || newVersion ? 'updates.checking-updates' : 'updates.update-not-available')}</p>
</div>
</Dialog>

<UpdateDownloadStatus
show={dialogType === 'updating'}
onCancel={() => {
cancelDownloadUpdate()
setDialogType('')
}}
show={showUpdateDownloadStatus}
onCancel={onCancel}
progress={updater.downloadProgress}
progressInfo={updater.progressInfo}
newVersion={updater.version}
Expand Down
57 changes: 49 additions & 8 deletions packages/neuron-ui/src/containers/Navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import React, { useCallback, useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
import { useLocation, NavLink, useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { useState as useGlobalState } from 'states'
import { NeuronWalletActions, useDispatch, useState as useGlobalState } from 'states'
import { checkForUpdates } from 'services/remote'
import { AppUpdater as AppUpdaterSubject } from 'services/subjects'
import Badge from 'widgets/Badge'
import Logo from 'widgets/Icons/Logo.png'
import {
Overview,
Expand Down Expand Up @@ -65,23 +68,48 @@ const MenuButton = ({
)
}

const ONE_DAY_MILLISECONDS = 24 * 3600 * 1000

const Navbar = () => {
const { pathname } = useLocation()
const dispatch = useDispatch()
const neuronWallet = useGlobalState()
const {
wallet: { name },
settings: { wallets = [] },
updater: { version },
} = neuronWallet
const [t, i18n] = useTranslation()
useOnLocaleChange(i18n)
const [selectedKey, setSelectedKey] = useState<string>()
const computedKey = menuItems.find(item => item.key === pathname || item.children?.some(v => v.key === pathname))?.key
const [isClickedSetting, setIsClickedSetting] = useState<boolean>(false)
const selectedKey = menuItems.find(item => item.key === pathname || item.children?.some(v => v.key === pathname))?.key

useEffect(() => {
if (computedKey) {
setSelectedKey(computedKey)
const onAppUpdaterUpdates = (info: Subject.AppUpdater) => {
dispatch({ type: NeuronWalletActions.UpdateAppUpdaterStatus, payload: info })
}
const appUpdaterSubscription = AppUpdaterSubject.subscribe(onAppUpdaterUpdates)

return () => {
appUpdaterSubscription.unsubscribe()
}
}, [computedKey])
}, [dispatch])

useEffect(() => {
checkForUpdates()
const interval = setInterval(() => {
checkForUpdates()
}, ONE_DAY_MILLISECONDS)
return () => {
clearInterval(interval)
}
}, [])

useEffect(() => {
if (pathname.includes(RoutePath.Settings)) {
setIsClickedSetting(true)
}
}, [pathname])

const [menuExpanded, setMenuExpanded] = useState(true)
const onClickExpand = useCallback(() => {
Expand Down Expand Up @@ -121,9 +149,18 @@ const Navbar = () => {
<React.Fragment key={item.key}>
<MenuButton menu={item} selectedKey={selectedKey}>
{item.icon}
<span>{t(item.name)}</span>

{!isClickedSetting && version && item.key === RoutePath.Settings ? (
<Badge>
<span>{t(item.name)}</span>
</Badge>
) : (
<span>{t(item.name)}</span>
)}

{item.children?.length && <ArrowOpenRight className={styles.arrow} />}
</MenuButton>

{item.children?.length && item.key === selectedKey && (
<div className={styles.child}>
<div className={styles.leftLine} />
Expand Down Expand Up @@ -162,7 +199,11 @@ const Navbar = () => {
placement={item.children?.length ? 'right-bottom' : 'right'}
>
<MenuButton menu={item} selectedKey={selectedKey}>
{item.icon}
{!isClickedSetting && version && item.key === RoutePath.Settings ? (
<Badge className={styles.unexpandedBadge}>{item.icon}</Badge>
) : (
item.icon
)}
</MenuButton>
</Tooltip>
</React.Fragment>
Expand Down
5 changes: 5 additions & 0 deletions packages/neuron-ui/src/containers/Navbar/navbar.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,8 @@ $hover-bg-color: #3cc68a4d;
margin: unset;
}
}

.unexpandedBadge::after {
top: 0;
right: 0;
}
2 changes: 1 addition & 1 deletion packages/neuron-ui/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@
"downloading-update": "Downloading update...",
"update-not-available": "There are currently no updates available.",
"updates-found-do-you-want-to-update": "An update ({{version}}) is available",
"install-update": "Install Update",
"install-update": "Immediate Update",
"updates-downloaded-about-to-quit-and-install": "Update downloaded. Ready to install and relaunch.",
"quit-and-install": "Install and relaunch"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/neuron-ui/src/locales/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@
"downloading-update": "正在下載更新…",
"update-not-available": "當前是最新版本",
"updates-found-do-you-want-to-update": "新版本 {{version}} 可供下載",
"install-update": "安裝更新",
"install-update": "立即更新",
"updates-downloaded-about-to-quit-and-install": "下載完成。請安裝新版本。",
"quit-and-install": "安裝並重啓應用"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/neuron-ui/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@
"downloading-update": "正在下载更新...",
"update-not-available": "当前是最新版本",
"updates-found-do-you-want-to-update": "新版本 {{version}} 可供下载",
"install-update": "安装更新",
"install-update": "立即更新",
"updates-downloaded-about-to-quit-and-install": "下载完成。请安装新版本。",
"quit-and-install": "安装并重启应用"
},
Expand Down
16 changes: 16 additions & 0 deletions packages/neuron-ui/src/widgets/Badge/badge.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@import '../../styles/theme.scss';

.badge {
position: relative;

&::after {
content: '';
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #ff1e1e;
position: absolute;
top: 20%;
margin-left: 4px;
}
}
9 changes: 9 additions & 0 deletions packages/neuron-ui/src/widgets/Badge/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react'
import { clsx } from 'utils'
import styles from './badge.module.scss'

const Badge = ({ children, className }: { children: React.ReactChild; className?: string }) => {
return <div className={clsx(styles.badge, className)}>{children}</div>
}

export default Badge
3 changes: 3 additions & 0 deletions packages/neuron-ui/src/widgets/Icons/Update.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading