Skip to content

Commit

Permalink
feat:Detect new upgrade on launch (#2783)
Browse files Browse the repository at this point in the history
* feat:Detect new upgrade on launch

* refactor: Refactor update logic

* feat: Add log for electron updater.

* Fix: Add badge for unexpanded and add icon for update

---------

Co-authored-by: Jun Ma <[email protected]>
  • Loading branch information
yanguoyu and alexsupa597 authored Aug 4, 2023
1 parent 0d23958 commit 34fff73
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 106 deletions.
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

2 comments on commit 34fff73

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Packaging for test is done in 5760722029

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Packaging for test is done in 5760722126

Please sign in to comment.