diff --git a/packages/neuron-ui/src/components/PageContainer/hooks.ts b/packages/neuron-ui/src/components/PageContainer/hooks.ts index 9ebd050afe..7122c13905 100644 --- a/packages/neuron-ui/src/components/PageContainer/hooks.ts +++ b/packages/neuron-ui/src/components/PageContainer/hooks.ts @@ -1,34 +1,77 @@ -import { useCallback, useEffect, useState } from 'react' +import { TFunction } from 'i18next' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { importedWalletDialogShown } from 'services/localCache' -import { isDark, openExternal, updateWallet, setTheme as setThemeAPI } from 'services/remote' +import { isDark, openExternal, setTheme as setThemeAPI, updateWalletStartBlockNumber } from 'services/remote' import { Migrate } from 'services/subjects' import { getExplorerUrl, isSuccessResponse } from 'utils' +const waitConfirmTime = 5 + +const useCountDown = (second: number) => { + const [countdown, setCountdown] = useState(0) + const countDecreaseIntervalRef = useRef>() + const resetCountDown = useCallback(() => { + clearInterval(countDecreaseIntervalRef.current) + setCountdown(second) + const decrement = () => { + countDecreaseIntervalRef.current = setInterval(() => { + setCountdown(v => { + if (v > 0) { + return v - 1 + } + clearInterval(countDecreaseIntervalRef.current) + return v + }) + }, 1_000) + } + decrement() + }, [setCountdown, countDecreaseIntervalRef]) + return { + countdown, + resetCountDown, + } +} + export const useSetBlockNumber = ({ firstAddress, walletID, isMainnet, isLightClient, isHomePage, + initStartBlockNumber, + headerTipNumber, + t, }: { firstAddress: string walletID: string isMainnet: boolean isLightClient: boolean isHomePage?: boolean + initStartBlockNumber?: number + headerTipNumber: number + t: TFunction }) => { const [isSetStartBlockShown, setIsSetStartBlockShown] = useState(false) const [startBlockNumber, setStartBlockNumber] = useState('') const [blockNumberErr, setBlockNumberErr] = useState('') - const onChangeStartBlockNumber = useCallback((e: React.SyntheticEvent) => { - const { value } = e.currentTarget - const blockNumber = value.replace(/,/g, '') - if (Number.isNaN(+blockNumber) || /[^\d]/.test(blockNumber)) { - return - } - setStartBlockNumber(blockNumber) - setBlockNumberErr('') - }, []) + const isSetLessThanBefore = useMemo( + () => !!(startBlockNumber && initStartBlockNumber && +startBlockNumber < initStartBlockNumber), + [initStartBlockNumber, startBlockNumber] + ) + const { countdown, resetCountDown } = useCountDown(waitConfirmTime) + const onChangeStartBlockNumber = useCallback( + (e: React.SyntheticEvent) => { + const { value } = e.currentTarget + const blockNumber = value.replaceAll(',', '') + if (Number.isNaN(+blockNumber)) { + return + } + setStartBlockNumber(+blockNumber > headerTipNumber ? headerTipNumber.toString() : blockNumber) + setBlockNumberErr(+blockNumber > headerTipNumber ? t('set-start-block-number.reset-to-header-tip-number') : '') + resetCountDown() + }, + [resetCountDown, initStartBlockNumber, headerTipNumber, t] + ) const onOpenAddressInExplorer = useCallback(() => { const explorerUrl = getExplorerUrl(isMainnet) openExternal(`${explorerUrl}/address/${firstAddress}`) @@ -38,7 +81,10 @@ export const useSetBlockNumber = ({ openExternal(`${explorerUrl}/block/${startBlockNumber}`) }, [startBlockNumber, isMainnet]) const onConfirm = useCallback(() => { - updateWallet({ id: walletID, startBlockNumber: `0x${BigInt(startBlockNumber).toString(16)}` }).then(res => { + updateWalletStartBlockNumber({ + id: walletID, + startBlockNumber: `0x${BigInt(startBlockNumber).toString(16)}`, + }).then(res => { if (isSuccessResponse(res)) { setIsSetStartBlockShown(false) } else { @@ -48,9 +94,9 @@ export const useSetBlockNumber = ({ }, [startBlockNumber, walletID]) const openDialog = useCallback(() => { setIsSetStartBlockShown(true) - setStartBlockNumber('') + setStartBlockNumber(initStartBlockNumber?.toString() ?? '') setBlockNumberErr('') - }, []) + }, [initStartBlockNumber]) useEffect(() => { if (isHomePage) { const needShow = importedWalletDialogShown.getStatus(walletID) @@ -70,6 +116,8 @@ export const useSetBlockNumber = ({ onViewBlock, onConfirm, blockNumberErr, + countdown, + isSetLessThanBefore, } } diff --git a/packages/neuron-ui/src/components/PageContainer/index.tsx b/packages/neuron-ui/src/components/PageContainer/index.tsx index b90cbc6575..7ee23aad18 100755 --- a/packages/neuron-ui/src/components/PageContainer/index.tsx +++ b/packages/neuron-ui/src/components/PageContainer/index.tsx @@ -8,7 +8,7 @@ import { getNetworkLabelI18nkey, } from 'utils' import Alert from 'widgets/Alert' -import { Close, NewTab } from 'widgets/Icons/icon' +import { Attention, Close, NewTab } from 'widgets/Icons/icon' import { ReactComponent as Sun } from 'widgets/Icons/Sun.svg' import { ReactComponent as Moon } from 'widgets/Icons/Moon.svg' import SyncStatusComponent from 'components/SyncStatus' @@ -45,7 +45,7 @@ const PageContainer: React.FC = props => { connectionStatus, networkID, }, - wallet: { addresses, id }, + wallet: { addresses, id, startBlockNumber: walletStartBlockNumber }, settings: { networks }, } = useGlobalState() const { children, head, notice, className, isHomePage } = props @@ -93,12 +93,18 @@ const PageContainer: React.FC = props => { onOpenAddressInExplorer, onViewBlock, onConfirm, + countdown, + isSetLessThanBefore, + blockNumberErr, } = useSetBlockNumber({ firstAddress: addresses[0]?.address, isMainnet, isLightClient, walletID: id, isHomePage, + initStartBlockNumber: walletStartBlockNumber ? Number(walletStartBlockNumber) : undefined, + headerTipNumber: bestKnownBlockNumber, + t, }) return (
@@ -134,6 +140,7 @@ const PageContainer: React.FC = props => { isMigrate={isMigrate} isLightClient={isLightClient} onOpenSetStartBlock={openDialog} + startBlockNumber={walletStartBlockNumber} />
@@ -148,31 +155,45 @@ const PageContainer: React.FC = props => {
{children}
-

{t('set-start-block-number.tip')}

- - {t('set-start-block-number.view-block')} - - - ) : ( - - ) - } - /> +
+ + {t('set-start-block-number.warn')} +
+
+

{t('set-start-block-number.tip')}

+ + {t('set-start-block-number.view-block')} + + + ) : ( + + ) + } + /> + {isSetLessThanBefore ? ( + + {t('set-start-block-number.set-less-than-before')} + + ) : null} +
) diff --git a/packages/neuron-ui/src/components/PageContainer/pageContainer.module.scss b/packages/neuron-ui/src/components/PageContainer/pageContainer.module.scss index 9113944a90..8c6fb0278a 100755 --- a/packages/neuron-ui/src/components/PageContainer/pageContainer.module.scss +++ b/packages/neuron-ui/src/components/PageContainer/pageContainer.module.scss @@ -144,9 +144,32 @@ } } -.startBlockTip { - font-weight: 400; - color: var(--secondary-text-color); +.setBlockContent { + padding: 0 0 20px 0; + overflow-y: auto; + + .content { + padding: 0 16px 0 16px; + } + .startBlockTip { + font-weight: 400; + color: var(--secondary-text-color); + } + + .setBlockWarn { + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--warn-background-color); + color: var(--warn-text-color); + font-size: 12px; + font-weight: 500; + + & > svg { + margin-right: 4px; + } + } } .viewAction { diff --git a/packages/neuron-ui/src/components/SyncStatus/index.tsx b/packages/neuron-ui/src/components/SyncStatus/index.tsx index 6ff0fb9f70..01a7d4ccac 100644 --- a/packages/neuron-ui/src/components/SyncStatus/index.tsx +++ b/packages/neuron-ui/src/components/SyncStatus/index.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import { SyncStatus as SyncStatusEnum, ConnectionStatus } from 'utils' +import { SyncStatus as SyncStatusEnum, ConnectionStatus, clsx, localNumberFormatter } from 'utils' import { Confirming, NewTab } from 'widgets/Icons/icon' import { ReactComponent as UnexpandStatus } from 'widgets/Icons/UnexpandStatus.svg' import { ReactComponent as StartBlock } from 'widgets/Icons/StartBlock.svg' @@ -13,12 +13,14 @@ const SyncDetail = ({ onOpenValidTarget, isLightClient, onOpenSetStartBlock, + startBlockNumber, }: { syncBlockNumbers: string isLookingValidTarget: boolean onOpenValidTarget: (e: React.SyntheticEvent) => void isLightClient: boolean onOpenSetStartBlock: () => void + startBlockNumber?: string }) => { const [t] = useTranslation() return ( @@ -42,10 +44,16 @@ const SyncDetail = ({ )} + {isLightClient && startBlockNumber ? ( +
+
{t('network-status.tooltip.start-block-number')}:
+
{localNumberFormatter(startBlockNumber)}
+
+ ) : null} {isLightClient && (
void + startBlockNumber?: string }>) => { const [t] = useTranslation() const [isOpen, setIsOpen] = useState(false) @@ -116,6 +126,7 @@ const SyncStatus = ({ onOpenValidTarget={onOpenValidTarget} isLightClient={isLightClient} onOpenSetStartBlock={onOpenSetStartBlock} + startBlockNumber={startBlockNumber} /> } trigger="click" diff --git a/packages/neuron-ui/src/components/SyncStatus/syncStatus.module.scss b/packages/neuron-ui/src/components/SyncStatus/syncStatus.module.scss index 94451a610e..9afd0e19e6 100755 --- a/packages/neuron-ui/src/components/SyncStatus/syncStatus.module.scss +++ b/packages/neuron-ui/src/components/SyncStatus/syncStatus.module.scss @@ -63,7 +63,7 @@ font-size: 12px; font-weight: 400; - &:nth-child(2) { + &.setStartBlockNumber { &::before { content: ''; display: block; @@ -74,10 +74,6 @@ } } - &:nth-child(3) { - margin-top: 12px; - } - & > div { display: flex; align-items: center; @@ -95,6 +91,19 @@ } } } + + .startBlockNumber { + margin-top: 8px; + .title { + font-size: 12px; + font-weight: 400; + } + .number { + color: var(--third-text-color); + font-family: D-DIN-PRO; + font-weight: 700; + } + } } .redDot { diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index e0d6782f8f..e645dd6fea 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -31,6 +31,7 @@ "tooltip": { "block-synced": "Block Synced", "looking-valid-target": "Looking for assumed valid target", + "start-block-number": "Start block number", "set-start-block-number": "Set start block number" }, "migrating": "migrating..." @@ -1124,10 +1125,13 @@ }, "set-start-block-number": { "title": "Set the start block number", + "warn": "Warning: Transactions before the start block of synchronization will not be synchronized", "tip": "The start sync block number is configuration when use light client ckb node, then you can set it and quick the sync", "input-place-holder": "Please enter the start sync block number, eg: 10,100,101", "locate-first-tx": "Locate the first transaction", - "view-block": "View the block" + "view-block": "View the block", + "set-less-than-before": "The block number you set is smaller than the previous set, the synchronization will restart, please proceed with caution.", + "reset-to-header-tip-number": "Unable to set a block number greater than the header tip block number, reset to the header tip block number." }, "main": { "external-node-detected-dialog": { diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index e7445298cc..e4834e8f5c 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -31,6 +31,7 @@ "tooltip": { "block-synced": "Bloc synchronisé", "looking-valid-target": "Recherche de la cible valide présumée", + "start-block-number": "bloc de départ", "set-start-block-number": "Définir le numéro de bloc de départ" }, "migrating": "migration en cours..." @@ -1124,10 +1125,13 @@ }, "set-start-block-number": { "title": "Définir le numéro de bloc de départ", + "warn": "Veuillez noter que les transactions antérieures à la synchronisation du bloc initial ne seront pas synchronisées !", "tip": "Le numéro de bloc de départ est une configuration lorsque vous utilisez le noeud CKB en mode client léger. Vous pouvez le définir et accélérer la synchronisation.", "input-place-holder": "Veuillez entrer le numéro de bloc de départ pour la synchronisation, par exemple : 10, 100, 101", "locate-first-tx": "Localiser la première transaction", - "view-block": "Voir le bloc" + "view-block": "Voir le bloc", + "set-less-than-before": "Le numéro de bloc que vous avez défini est inférieur au bloc précédent. La synchronisation recommencera, veuillez procéder avec prudence.", + "reset-to-header-tip-number": "Impossible de définir un numéro de bloc supérieur au bloc le plus récent. Réinitialisation au numéro de bloc le plus récent." }, "main": { "external-node-detected-dialog": { diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 44b240120b..dcd8002154 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -31,6 +31,7 @@ "tooltip": { "block-synced": "已同步區塊", "looking-valid-target": "尋找預設可信區塊", + "start-block-number": "起始區塊", "set-start-block-number": "設置同步起始區塊" }, "migrating": "正在數據遷移..." @@ -1095,10 +1096,13 @@ }, "set-start-block-number": { "title": "設置同步起始區塊", + "warn": "請註意,同步起始區塊之前的交易不會被同步!", "tip": "輕客戶端模式下的訂閱起始點是可配置的,因此您可以設置同步起始區塊以加快同步速度。", "input-place-holder": "輸入同步起始區塊高度,例如:10,100,101", "locate-first-tx": "定位第一筆交易", - "view-block": "查看區塊" + "view-block": "查看區塊", + "set-less-than-before": "您設置的區塊號小於之前區塊,同步將重新開始,請謹慎操作。", + "reset-to-header-tip-number": "無法設置比最新區塊更大的區塊號,已重置為最新區塊號。" }, "main": { "external-node-detected-dialog": { diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 6a5b89f526..482129275e 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -31,6 +31,7 @@ "tooltip": { "block-synced": "已同步区块", "looking-valid-target": "寻找预设可信区块", + "start-block-number": "起始区块", "set-start-block-number": "设置同步起始区块" }, "migrating": "正在数据迁移..." @@ -1116,10 +1117,13 @@ }, "set-start-block-number": { "title": "设置同步起始区块", + "warn": "请注意,同步起始区块之前的交易不会被同步!", "tip": "轻客户端模式下的订阅起始点是可配置的,因此您可以设置同步起始区块以加快同步速度。", "input-place-holder": "输入同步起始区块高度,例如:10,100,101", "locate-first-tx": "定位第一笔交易", - "view-block": "查看区块" + "view-block": "查看区块", + "set-less-than-before": "您设置的区块号小于之前的设定,同步将重新开始,请谨慎操作。", + "reset-to-header-tip-number": "无法设置比最新区块更大的区块号,已重置为最新区块号。" }, "main": { "external-node-detected-dialog": { diff --git a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts index 3ca2ffdff6..c9ab2831e6 100644 --- a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts +++ b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts @@ -60,6 +60,7 @@ type Action = | 'update-wallet' | 'delete-wallet' | 'backup-wallet' + | 'update-wallet-start-block-number' | 'get-all-addresses' | 'update-address-description' | 'request-password' diff --git a/packages/neuron-ui/src/services/remote/wallets.ts b/packages/neuron-ui/src/services/remote/wallets.ts index 89da4ef168..9c1e496c66 100644 --- a/packages/neuron-ui/src/services/remote/wallets.ts +++ b/packages/neuron-ui/src/services/remote/wallets.ts @@ -9,6 +9,9 @@ export const createWallet = remoteApi('create-wal export const updateWallet = remoteApi('update-wallet') export const deleteWallet = remoteApi('delete-wallet') export const backupWallet = remoteApi('backup-wallet') +export const updateWalletStartBlockNumber = remoteApi( + 'update-wallet-start-block-number' +) export const getAddressesByWalletID = remoteApi('get-all-addresses') export const updateAddressDescription = remoteApi('update-address-description') diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index 243fd982de..9c1ac509e2 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -202,6 +202,7 @@ declare namespace State { device?: DeviceInfo isHD?: boolean isWatchOnly?: boolean + startBlockNumber?: string } interface DeviceInfo { diff --git a/packages/neuron-ui/src/types/Controller/index.d.ts b/packages/neuron-ui/src/types/Controller/index.d.ts index 35ecc4f87b..a0a743e90f 100644 --- a/packages/neuron-ui/src/types/Controller/index.d.ts +++ b/packages/neuron-ui/src/types/Controller/index.d.ts @@ -35,7 +35,11 @@ declare namespace Controller { newPassword?: string name?: string device?: any - startBlockNumber?: string + } + + interface UpdateWalletStartBlockNumberParams { + id: string + startBlockNumber: string } interface RequestPasswordParams { diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index 0d92bb8294..d7d3198f05 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -64,6 +64,7 @@ import { resetSyncTaskQueue } from '../block-sync-renderer' import DataUpdateSubject from '../models/subjects/data-update' import CellManagement from './cell-management' import { UpdateCellLocalInfo } from '../database/chain/entities/cell-local-info' +import { CKBLightRunner } from '../services/light-runner' export type Command = 'export-xpubkey' | 'import-xpubkey' | 'delete-wallet' | 'backup-wallet' | 'migrate-acp' // Handle channel messages from renderer process and user actions. @@ -332,19 +333,45 @@ export default class ApiController { }) handle( - 'update-wallet', + 'update-wallet-start-block-number', async ( _, - params: { id: string; password?: string; name?: string; newPassword?: string; startBlockNumber?: string } + params: { + id: string + startBlockNumber: string + } ) => { const res = this.#walletsController.update(params) - if (params.startBlockNumber) { + const network = NetworksService.getInstance().getCurrent() + if (network.type !== NetworkType.Light) { + throw new Error('Only Light client can set start block number') + } + const lastSetStartBlockNumber = WalletsService.getInstance().getCurrent()?.toJSON().startBlockNumber + if (lastSetStartBlockNumber && +params.startBlockNumber < +lastSetStartBlockNumber) { + await CKBLightRunner.getInstance().clearNodeCache() + } else { resetSyncTaskQueue.asyncPush(true) } return res } ) + handle( + 'update-wallet', + async ( + _, + params: { + id: string + password?: string + name?: string + newPassword?: string + } + ) => { + const res = this.#walletsController.update(params) + return res + } + ) + handle('delete-wallet', async (_, { id = '', password = '' }) => { return this.#walletsController.delete({ id, password }) })