From 889f9a6d10b0017fc42986651f0ea9e2c9466d60 Mon Sep 17 00:00:00 2001 From: yanguoyu <841185308@qq.com> Date: Tue, 28 Jun 2022 21:47:39 +0800 Subject: [PATCH 1/8] feat: Add setting for ckb-node and indexer data path. --- .../src/components/ClearCache/index.tsx | 41 ++++---- .../components/ClearCache/style.module.scss | 48 ++++++---- .../src/components/DataSetting/hooks.ts | 70 ++++++++++++++ .../components/DataSetting/index.module.scss | 57 +++++++++++ .../src/components/DataSetting/index.tsx | 82 ++++++++++++++++ .../src/components/ErrorBoundary/index.tsx | 4 +- .../src/components/GeneralSetting/index.tsx | 6 +- .../src/components/SettingTabs/index.tsx | 3 + packages/neuron-ui/src/locales/en.json | 23 ++++- packages/neuron-ui/src/locales/zh-tw.json | 23 ++++- packages/neuron-ui/src/locales/zh.json | 23 ++++- packages/neuron-ui/src/services/remote/app.ts | 17 +++- .../src/services/remote/remoteApiWrapper.ts | 7 ++ .../src/stories/GeneralSetting.stories.tsx | 2 +- packages/neuron-ui/src/utils/enums.ts | 1 + packages/neuron-ui/src/widgets/Icons/icon.tsx | 2 + packages/neuron-wallet/package.json | 2 +- packages/neuron-wallet/src/controllers/api.ts | 48 ++++++++++ .../src/controllers/export-debug.ts | 4 +- .../neuron-wallet/src/controllers/mercury.ts | 4 +- .../neuron-wallet/src/services/ckb-runner.ts | 13 +-- .../neuron-wallet/src/services/indexer.ts | 8 +- .../src/services/monitor/base.ts | 55 +++++++---- .../services/monitor/ckb-indexer-monitor.ts | 4 + .../src/services/monitor/ckb-monitor.ts | 5 + .../src/services/monitor/index.ts | 27 +++++- .../neuron-wallet/src/services/settings.ts | 31 +++++- .../tx-status-listener.intg.test.ts | 4 +- .../tests/controllers/asset-account.test.ts | 8 ++ .../tests/controllers/export-debug.test.ts | 5 +- .../tests/controllers/multisig.test.ts | 2 +- .../tests/services/ckb-runner.test.ts | 7 ++ .../tests/services/monitor.test.ts | 72 +++++++++++++- .../tests/services/setting.test.ts | 95 +++++++++++++++++++ 34 files changed, 692 insertions(+), 111 deletions(-) create mode 100644 packages/neuron-ui/src/components/DataSetting/hooks.ts create mode 100644 packages/neuron-ui/src/components/DataSetting/index.module.scss create mode 100644 packages/neuron-ui/src/components/DataSetting/index.tsx create mode 100644 packages/neuron-wallet/tests/services/setting.test.ts diff --git a/packages/neuron-ui/src/components/ClearCache/index.tsx b/packages/neuron-ui/src/components/ClearCache/index.tsx index b2b19a5eb5..8e9294d935 100644 --- a/packages/neuron-ui/src/components/ClearCache/index.tsx +++ b/packages/neuron-ui/src/components/ClearCache/index.tsx @@ -2,13 +2,13 @@ import React, { useEffect, useCallback, useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import Button from 'widgets/Button' import Spinner from 'widgets/Spinner' -import { ReactComponent as Attention } from 'widgets/Icons/Attention.svg' import WarningIcon from 'widgets/Icons/Warning.png' import { StateDispatch, addPopup } from 'states' import { clearCellCache } from 'services/remote' import { cacheClearDate } from 'services/localCache' import { isSuccessResponse, useDialog, uniformTimeFormatter } from 'utils' +import { Tooltip } from 'widgets/Icons/icon' import styles from './style.module.scss' const I18N_PATH = 'settings.clear-cache' @@ -71,28 +71,25 @@ const ClearCache = ({ dispatch }: { dispatch: StateDispatch }) => { return ( <> -
- {clearedDate ? ( -
{t('settings.general.cache-cleared-on', { date: clearedDate })}
- ) : null} -
- - {t('settings.general.clear-cache-description')} -
-
-
- +
+ {t('settings.data.cache')} + + + + :
+
{t('settings.data.cache-cleared-on', { date: clearedDate })}
+ warning
{t(`${I18N_PATH}.title`)}
diff --git a/packages/neuron-ui/src/components/ClearCache/style.module.scss b/packages/neuron-ui/src/components/ClearCache/style.module.scss index 937589c6e9..1b74c20c9a 100644 --- a/packages/neuron-ui/src/components/ClearCache/style.module.scss +++ b/packages/neuron-ui/src/components/ClearCache/style.module.scss @@ -1,30 +1,36 @@ @import '../../styles//mixin.scss'; .clearCache { - &.detail { - grid-area: clear-cache-detail; - .date { - display: flex; - align-items: center; - font-size: 0.875rem; - height: 1.125rem; - margin-bottom: 5px; - } - .desc { - display: flex; - font-size: 0.6875rem; - color: #666; + font-weight: bold; + display: flex; + + .tooltip { + position: relative; + display: inline-flex; + align-items: center; + padding-left: 4px; + + &::after { + display: none; + position: absolute; + left: -20px; + top: 120%; + content: attr(data-tooltip); + padding: 4px 12px; + border-radius: 6px; + background: #cccccc99; + font-weight: 300; + width: 600px; } - svg { - height: 0.6875rem; - width: 0.6875rem; - filter: grayscale(1) opacity(0.6); - margin: 2px 5px 0 0; + + &:hover::after { + display: block; } } - &.action { - grid-area: clear-cache-action; - } +} + +.clearedDate { + font-size: 12px; } .dialog { diff --git a/packages/neuron-ui/src/components/DataSetting/hooks.ts b/packages/neuron-ui/src/components/DataSetting/hooks.ts new file mode 100644 index 0000000000..44a07d733f --- /dev/null +++ b/packages/neuron-ui/src/components/DataSetting/hooks.ts @@ -0,0 +1,70 @@ +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + getCkbNodeDataPath, + getIndexerDataPath, + invokeShowOpenDialog, + startProcessMonitor, + stopProcessMonitor, + setCkbNodeDataPath, + setIndexerDataPath, +} from 'services/remote' +import { isSuccessResponse, useDialogWrapper, useDidMount } from 'utils' + +export const useDataPath = ( + getPath: typeof getCkbNodeDataPath | typeof getIndexerDataPath, + setPath: typeof setCkbNodeDataPath | typeof setIndexerDataPath, + type: Parameters[0] +) => { + const [t] = useTranslation() + const [prevPath, setPrevPath] = useState() + const [currentPath, setCurrentPath] = useState() + const { dialogRef, openDialog, closeDialog } = useDialogWrapper() + useDidMount(() => { + getPath(undefined).then(res => { + if (isSuccessResponse(res)) { + setPrevPath(res.result!) + } + }) + }) + const onSetting = useCallback(() => { + invokeShowOpenDialog({ + buttonLabel: t('settings.data.set', { lng: navigator.language }), + properties: ['openDirectory', 'createDirectory', 'promptToCreate', 'treatPackageAsDirectory'], + }).then(res => { + if (isSuccessResponse(res) && !res.result?.canceled && res.result?.filePaths?.length) { + setCurrentPath(res.result?.filePaths?.[0]) + stopProcessMonitor(type).then(stopRes => { + if (isSuccessResponse(stopRes)) { + openDialog() + } + }) + } + }) + }, [t, type]) + const onCancel = useCallback(() => { + startProcessMonitor(type).then(res => { + if (isSuccessResponse(res)) { + closeDialog() + } + }) + }, [closeDialog, type]) + const onConfirm = useCallback(() => { + setPath(currentPath!).then(res => { + if (isSuccessResponse(res)) { + setPrevPath(currentPath) + closeDialog() + } + }) + }, [currentPath, closeDialog, setPrevPath, setPath]) + return { + prevPath, + currentPath, + onSetting, + dialogRef, + onCancel, + onConfirm, + } +} + +export default useDataPath diff --git a/packages/neuron-ui/src/components/DataSetting/index.module.scss b/packages/neuron-ui/src/components/DataSetting/index.module.scss new file mode 100644 index 0000000000..78f051afad --- /dev/null +++ b/packages/neuron-ui/src/components/DataSetting/index.module.scss @@ -0,0 +1,57 @@ +@import '../../styles/mixin.scss'; + +.root { + margin-top: 20px; + display: grid; + grid-template-columns: auto 1fr 140px; + grid-gap: 30px 20px; + align-items: center; + + .name { + font-weight: bold; + } + + .content { + font-size: 12px; + word-break: break-all; + } + + button { + line-height: 1; + height: 1.625rem; + } +} + +.dialog { + @include dialog-container; + padding: 30px 50px; + + .describe { + word-break: break-all; + font-size: 14px; + } + + .attention { + position: relative; + padding-left: 1rem; + font-size: 0.75rem; + letter-spacing: 0.5px; + margin: 14px 0 20px 0; + color: #666; + + svg { + position: absolute; + left: 0; + top: 0.13rem; + filter: grayscale(1) opacity(0.6); + width: 0.625rem; + height: 0.625rem; + } + } + + .action { + padding-top: 16px; + display: flex; + justify-content: space-between; + } +} \ No newline at end of file diff --git a/packages/neuron-ui/src/components/DataSetting/index.tsx b/packages/neuron-ui/src/components/DataSetting/index.tsx new file mode 100644 index 0000000000..5836e7fd36 --- /dev/null +++ b/packages/neuron-ui/src/components/DataSetting/index.tsx @@ -0,0 +1,82 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' +import Button from 'widgets/Button' +import ClearCache from 'components/ClearCache' +import { useDispatch } from 'states' +import { getCkbNodeDataPath, getIndexerDataPath, setCkbNodeDataPath, setIndexerDataPath } from 'services/remote' +import { ReactComponent as Attention } from 'widgets/Icons/ExperimentalAttention.svg' +import CopyZone from 'widgets/CopyZone' +import { useDataPath } from './hooks' + +import styles from './index.module.scss' + +const itemProps: Record< + 'ckbNode' | 'indexer', + { + type: 'ckb' | 'ckb-indexer' + getPath: typeof getCkbNodeDataPath | typeof getIndexerDataPath + setPath: typeof setCkbNodeDataPath | typeof setIndexerDataPath + titleI18nKey: string + tipI18nKey: string + } +> = { + ckbNode: { + type: 'ckb', + getPath: getCkbNodeDataPath, + setPath: setCkbNodeDataPath, + titleI18nKey: 'ckb-node-data', + tipI18nKey: 'remove-ckb-data-tip', + }, + indexer: { + type: 'ckb-indexer', + getPath: getIndexerDataPath, + setPath: setIndexerDataPath, + titleI18nKey: 'ckb-indexer-data', + tipI18nKey: 'remove-indexer-data-tip', + }, +} + +const SetItem = ({ type }: { type: keyof typeof itemProps }) => { + const props = itemProps[type] + const [t] = useTranslation() + const { onSetting, prevPath, currentPath, dialogRef, onCancel, onConfirm } = useDataPath( + props.getPath, + props.setPath, + props.type + ) + return ( + <> +
{t(`settings.data.${props.titleI18nKey}`)}:
+ + {prevPath} + +
+ + + ) +} + +const DataSetting = () => { + const dispatch = useDispatch() + return ( +
+ + + +
+ ) +} + +DataSetting.displayName = 'DataSetting' +export default DataSetting diff --git a/packages/neuron-ui/src/components/ErrorBoundary/index.tsx b/packages/neuron-ui/src/components/ErrorBoundary/index.tsx index 409a34f660..bed246d94c 100644 --- a/packages/neuron-ui/src/components/ErrorBoundary/index.tsx +++ b/packages/neuron-ui/src/components/ErrorBoundary/index.tsx @@ -5,7 +5,9 @@ import { handleViewError } from 'services/remote' const handleError = (error: Error) => { handleViewError(error.toString()) - window.location.reload() + if (process.env.NODE_ENV !== 'development') { + window.location.reload() + } return { hasError: true } } diff --git a/packages/neuron-ui/src/components/GeneralSetting/index.tsx b/packages/neuron-ui/src/components/GeneralSetting/index.tsx index 133119882f..6b973b5834 100644 --- a/packages/neuron-ui/src/components/GeneralSetting/index.tsx +++ b/packages/neuron-ui/src/components/GeneralSetting/index.tsx @@ -1,11 +1,9 @@ import React, { useCallback, useState, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { ProgressIndicator } from 'office-ui-fabric-react' -import ClearCache from 'components/ClearCache' import Button from 'widgets/Button' import Spinner from 'widgets/Spinner' import Dropdown from 'widgets/Dropdown' -import { StateDispatch } from 'states' import { checkForUpdates, downloadUpdate, installUpdate, setLocale, getVersion } from 'services/remote' import { CONSTANTS } from 'utils' @@ -72,10 +70,9 @@ const UpdateDownloadStatus = ({ progress = 0, newVersion = '', releaseNotes = '' interface GeneralSettingProps { updater: State.AppUpdater - dispatch: StateDispatch } -const GeneralSetting = ({ updater, dispatch }: GeneralSettingProps) => { +const GeneralSetting = ({ updater }: GeneralSettingProps) => { const [t, i18n] = useTranslation() const [lng, setLng] = useState(i18n.language) @@ -127,7 +124,6 @@ const GeneralSetting = ({ updater, dispatch }: GeneralSettingProps) => { )} -
{t('settings.general.language')}
{ diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 8f2f3a889b..17c51a7193 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -312,17 +312,14 @@ "setting-tabs": { "general": "General", "wallets": "Wallets", - "network": "Network" + "network": "Network", + "data": "Data" }, "general": { - "clear-cache": "Clear Cache", - "clearing-cache": "Clearing...", - "clear-cache-description": "Clear cache if you encounter data sync or balance display problems. Neuron will rescan block data.", "show": "Show", "hide": "Hide", "version": "Version", "language": "Language", - "cache-cleared-on": "Cache cleared on {{date}}", "apply": "Apply" }, "wallet-manager": { @@ -372,6 +369,22 @@ "cancel": "Cancel", "ok": "OK" } + }, + "data": { + "ckb-node-data": "CKB Node Data", + "ckb-indexer-data": "CKB Indexer Data", + "set": "Set", + "cache": "Cache", + "clear-cache-description": "Clear cache if you encounter data sync or balance display problems. Neuron will rescan block data.", + "cache-cleared-on": "Cache cleared on {{date}}", + "clear-cache": "Clear Cache", + "clearing-cache": "Clearing...", + "remove-ckb-data-tip": "Please move CKB Node Data from {{prevPath}} to {{currentPath}} and then click on \"Data have been moved\" to start from the previously synchronized block; Or click on \"Synchronize from scratch\" to start with synchronizing from scratch.", + "remove-indexer-data-tip": "Please move Indexer Data from {{prevPath}} to {{currentPath}} and then click on \"Data have been moved\" to start from the previously synchronized block; Or click on \"Synchronize from scratch\" to start with synchronizing from scratch.", + "resync-describe": "Attention: Synchronize from scratch won't remove previously downloaded CKB Node(or Indexer) Data.", + "cancel": "Cancel", + "move-data-finish": "Data have been moved", + "re-sync": "Synchronize from scratch" } }, "password-request": { diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index c200a46f37..38fc1441ac 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -305,17 +305,14 @@ "setting-tabs": { "general": "通用", "wallets": "錢包", - "network": "網絡" + "network": "網絡", + "data": "數據" }, "general": { - "clear-cache": "清理緩存", - "clearing-cache": "清理中…", - "clear-cache-description": "當資料同步或餘額顯示出現問題時,可以清理緩存,Neuron 會重新同步所有塊數據。", "show": "顯示", "hide": "隱藏", "version": "版本", "language": "語言", - "cache-cleared-on": "上次清理緩存時間 {{date}}", "apply": "應用" }, "wallet-manager": { @@ -365,6 +362,22 @@ "cancel": "取消", "ok": "確認" } + }, + "data": { + "ckb-node-data": "節點數據", + "ckb-indexer-data": "Indexer 數據", + "set": "設置", + "cache": "緩存", + "clear-cache-description": "當資料同步或餘額顯示出現問題時,可以清理緩存,Neuron 會重新同步所有塊數據。", + "cache-cleared-on": "上次清理緩存時間 {{date}}", + "clear-cache": "清理緩存", + "clearing-cache": "清理中…", + "remove-ckb-data-tip": "請將 CKB Node 數據從 {{prevPath}} 移動至 {{currentPath}}然後點擊 \"數據轉移完成\" 開始從已知區塊同步; 否則點擊 \"重新同步\" 開始重新同步.", + "remove-indexer-data-tip": "請將 Indexer 數據從 {{prevPath}} 移動至 {{currentPath}}然後點擊 \"數據轉移完成\" 開始從已知區塊同步; 否則點擊 \"重新同步\" 開始重新同步.", + "cancel": "取消", + "resync-describe": "註意: 重新同步不會刪除當前已同步的 CKB Node(Indexer) 數據.", + "move-data-finish": "數據轉移完成", + "re-sync": "重新同步" } }, "password-request": { diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 050eed4f4f..e1446c1018 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -305,17 +305,14 @@ "setting-tabs": { "general": "通用", "wallets": "钱包", - "network": "网络" + "network": "网络", + "data": "数据" }, "general": { - "clear-cache": "清空缓存", - "clearing-cache": "清空中...", - "clear-cache-description": "当数据同步或余额显示出现问题时,可以清空缓存,Neuron 会重新同步所有块数据。", "show": "显示", "hide": "隐藏", "version": "版本", "language": "语言", - "cache-cleared-on": "上次清空缓存时间 {{date}}", "apply": "应用" }, "wallet-manager": { @@ -365,6 +362,22 @@ "cancel": "取消", "ok": "确认" } + }, + "data": { + "ckb-node-data": "节点数据", + "ckb-indexer-data": "Indexer 数据", + "set": "设置", + "cache": "缓存", + "clear-cache-description": "当数据同步或余额显示出现问题时,可以清空缓存,Neuron 会重新同步所有块数据。", + "cache-cleared-on": "上次清空缓存时间 {{date}}", + "clear-cache": "清空缓存", + "clearing-cache": "清空中...", + "remove-ckb-data-tip": "请将 CKB Node 数据从 {{prevPath}} 移动至 {{currentPath}}然后点击 \"数据转移完成\" 开始从已知区块同步; 否则点击 \"重新同步\" 开始重新同步.", + "remove-indexer-data-tip": "请将 Indexer 数据从 {{prevPath}} 移动至 {{currentPath}}然后点击 \"数据转移完成\" 开始从已知区块同步; 否则点击 \"重新同步\" 开始重新同步.", + "resync-describe": "注意: 重新同步不会删除当前已同步的 CKB Node(Indexer) 数据.", + "cancel": "取消", + "move-data-finish": "数据转移完成", + "re-sync": "重新同步" } }, "password-request": { diff --git a/packages/neuron-ui/src/services/remote/app.ts b/packages/neuron-ui/src/services/remote/app.ts index 6b0f8a050b..6b22c96a06 100644 --- a/packages/neuron-ui/src/services/remote/app.ts +++ b/packages/neuron-ui/src/services/remote/app.ts @@ -1,4 +1,12 @@ -import { OpenDialogOptions, MenuItemConstructorOptions, MenuItem, Size, OpenDialogReturnValue } from 'electron' +import { + OpenDialogOptions, + MenuItemConstructorOptions, + MenuItem, + Size, + OpenDialogReturnValue, + MessageBoxOptions, + MessageBoxReturnValue, +} from 'electron' import { LOCALES } from 'utils/const' import { remoteApi } from './remoteApiWrapper' @@ -9,6 +17,12 @@ export const requestOpenInExplorer = remoteApi('handle-view-error') export const showSettings = remoteApi('show-settings') export const setLocale = remoteApi('set-locale') +export const getCkbNodeDataPath = remoteApi('get-ckb-node-data-path') +export const setCkbNodeDataPath = remoteApi('set-ckb-node-data-path') +export const getIndexerDataPath = remoteApi('get-indexer-data-path') +export const setIndexerDataPath = remoteApi('set-indexer-data-path') +export const stopProcessMonitor = remoteApi<'ckb' | 'ckb-indexer'>('stop-process-monitor') +export const startProcessMonitor = remoteApi<'ckb' | 'ckb-indexer'>('start-process-monitor') export const clearCellCache = remoteApi('clear-cache') @@ -17,3 +31,4 @@ export const invokeShowOpenDialog = remoteApi('show-open-dialog-modal') export const invokeOpenContextMenu = remoteApi>('open-context-menu') export const invokeGetAllDisplaysSize = remoteApi('get-all-displays-size') +export const invokeShowMessageBox = remoteApi('show-message-box') diff --git a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts index 864805a6d9..9895319787 100644 --- a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts +++ b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts @@ -42,6 +42,13 @@ type Action = | 'open-external' | 'open-context-menu' | 'get-all-displays-size' + | 'show-message-box' + | 'get-ckb-node-data-path' + | 'set-ckb-node-data-path' + | 'get-indexer-data-path' + | 'set-indexer-data-path' + | 'stop-process-monitor' + | 'start-process-monitor' // Wallets | 'get-all-wallets' | 'get-current-wallet' diff --git a/packages/neuron-ui/src/stories/GeneralSetting.stories.tsx b/packages/neuron-ui/src/stories/GeneralSetting.stories.tsx index f84b7b4236..a4ea98e16c 100644 --- a/packages/neuron-ui/src/stories/GeneralSetting.stories.tsx +++ b/packages/neuron-ui/src/stories/GeneralSetting.stories.tsx @@ -20,5 +20,5 @@ stories.addDecorator(withKnobs).add('With knobs', () => { const props = { ...initStates, } - return {}} /> + return }) diff --git a/packages/neuron-ui/src/utils/enums.ts b/packages/neuron-ui/src/utils/enums.ts index 6ae91c2e34..e9ba2227f4 100644 --- a/packages/neuron-ui/src/utils/enums.ts +++ b/packages/neuron-ui/src/utils/enums.ts @@ -18,6 +18,7 @@ export enum RoutePath { SettingsGeneral = '/settings/general', SettingsWallets = '/settings/wallets', SettingsNetworks = '/settings/networks', + SettingsData = '/settings/data', CreateWallet = '/wallets/new', ImportWallet = '/wallets/import', ImportKeystore = '/keystore/import', diff --git a/packages/neuron-ui/src/widgets/Icons/icon.tsx b/packages/neuron-ui/src/widgets/Icons/icon.tsx index 356fd75c23..a99013d86f 100644 --- a/packages/neuron-ui/src/widgets/Icons/icon.tsx +++ b/packages/neuron-ui/src/widgets/Icons/icon.tsx @@ -23,6 +23,7 @@ import { ReactComponent as KeystoreSvg } from './Keystore.svg' import { ReactComponent as SettingsSvg } from './Settings.svg' import { ReactComponent as PendingIconSvg } from './PendingIcon.svg' import { ReactComponent as NewTabSvg } from './new_tab.svg' +import { ReactComponent as TooltipSvg } from './Tooltip.svg' import styles from './icon.module.scss' @@ -60,3 +61,4 @@ export const Keystore = WrapSvg(KeystoreSvg) export const Settings = WrapSvg(SettingsSvg) export const PendingIcon = WrapSvg(PendingIconSvg) export const NewTab = WrapSvg(NewTabSvg) +export const Tooltip = WrapSvg(TooltipSvg) diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index 8b8b69cd08..bbb1d7626b 100644 --- a/packages/neuron-wallet/package.json +++ b/packages/neuron-wallet/package.json @@ -23,7 +23,7 @@ "start:dev": "yarn run build && electron .", "start:debug": "yarn run build && electron --inspect=5858 .", "clean": "rimraf dist/*", - "test": "jest --runInBand --collect-coverage --forceExit", + "test": "jest --runInBand --collect-coverage --forceExit packages/neuron-wallet/tests/services/setting.test.ts", "test:watch": "jest --watch", "lint": "eslint --fix --ext .ts,.js src", "precommit": "lint-staged", diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index ef9d2022fd..4a02555d1a 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -53,6 +53,7 @@ import SUDTController from "controllers/sudt" import SyncedBlockNumber from 'models/synced-block-number' import IndexerService from 'services/indexer' import MultisigConfigModel from 'models/multisig-config' +import startMonitor, { stopMonitor } from 'services/monitor' export type Command = 'export-xpubkey' | 'import-xpubkey' | 'delete-wallet' | 'backup-wallet' | 'migrate-acp' // Handle channel messages from renderer process and user actions. @@ -453,6 +454,53 @@ export default class ApiController { await IndexerService.clearCache(params?.resetIndexerData) return { status: ResponseCode.Success, result: true } }) + + handle('get-ckb-node-data-path', () => { + return { + status: ResponseCode.Success, + result: SettingsService.getInstance().ckbDataPath + } + }) + + handle('set-ckb-node-data-path', (_, dataPath: string) => { + SettingsService.getInstance().ckbDataPath = dataPath + startMonitor('ckb', true) + return { + status: ResponseCode.Success, + result: SettingsService.getInstance().ckbDataPath + } + }) + + handle('get-indexer-data-path', () => { + return { + status: ResponseCode.Success, + result: SettingsService.getInstance().indexerDataPath + } + }) + + handle('set-indexer-data-path', (_, dataPath: string) => { + SettingsService.getInstance().indexerDataPath = dataPath + startMonitor('ckb-indexer', true) + return { + status: ResponseCode.Success, + result: SettingsService.getInstance().ckbDataPath + } + }) + + handle('start-process-monitor', (_, monitorName: string) => { + startMonitor(monitorName, true) + return { + status: ResponseCode.Success, + } + }) + + handle('stop-process-monitor', async (_, monitorName: string) => { + await stopMonitor(monitorName) + return { + status: ResponseCode.Success, + } + }) + // Sign and Verify handle('sign-message', async (_, params: Controller.Params.SignParams) => { return this.#signAndVerifyController.sign(params) diff --git a/packages/neuron-wallet/src/controllers/export-debug.ts b/packages/neuron-wallet/src/controllers/export-debug.ts index 26de6c516b..39ce8aa3d3 100644 --- a/packages/neuron-wallet/src/controllers/export-debug.ts +++ b/packages/neuron-wallet/src/controllers/export-debug.ts @@ -6,11 +6,11 @@ import CKB from '@nervosnetwork/ckb-sdk-core' import { app, dialog } from 'electron' import logger from 'utils/logger' import { t } from 'i18next' -import { ckbDataPath } from 'services/ckb-runner' import NetworksService from 'services/networks' import SyncedBlockNumber from 'models/synced-block-number' import AddressService from 'services/addresses' import redistCheck from 'utils/redist-check' +import SettingsService from 'services/settings' export default class ExportDebugController { // eslint-disable-next-line prettier/prettier @@ -101,7 +101,7 @@ export default class ExportDebugController { const SIZE_TO_READ = 32_000 return new Promise((resolve, reject) => { - const logPath = path.resolve(ckbDataPath(), 'data', 'logs', 'run.log') + const logPath = path.resolve(SettingsService.getInstance().ckbDataPath, 'data', 'logs', 'run.log') if (!fs.existsSync(logPath)) { return reject(new Error("File not found")) } const fileStats = fs.statSync(logPath) diff --git a/packages/neuron-wallet/src/controllers/mercury.ts b/packages/neuron-wallet/src/controllers/mercury.ts index 0862839802..930d6395ac 100644 --- a/packages/neuron-wallet/src/controllers/mercury.ts +++ b/packages/neuron-wallet/src/controllers/mercury.ts @@ -1,14 +1,14 @@ -import { ckbDataPath } from 'services/ckb-runner' import path from 'path' import fs from 'fs' import env from 'env' import { dialog } from 'electron' import { t } from 'i18next' +import SettingsService from 'services/settings' const { app } = env export const migrate = async () => { - const ckbPath = ckbDataPath() + const ckbPath = SettingsService.getInstance().ckbDataPath const lumosDataPath = path.resolve(app.getPath('userData'), app.isPackaged ? '' : 'dev', 'indexer_data') const ckbIndexerDataPath = path.resolve(app.getPath('userData'), app.isPackaged ? '' : 'dev', 'ckb-indexer') diff --git a/packages/neuron-wallet/src/services/ckb-runner.ts b/packages/neuron-wallet/src/services/ckb-runner.ts index 0716b94d55..a7eec6a131 100644 --- a/packages/neuron-wallet/src/services/ckb-runner.ts +++ b/packages/neuron-wallet/src/services/ckb-runner.ts @@ -4,6 +4,7 @@ import fs from 'fs' import { ChildProcess, spawn } from 'child_process' import process from 'process' import logger from 'utils/logger' +import SettingsService from './settings' const platform = (): string => { switch (process.platform) { @@ -30,19 +31,15 @@ const ckbBinary = (): string => { return platform() === 'win' ? binary + '.exe' : binary } -export const ckbDataPath = (): string => { - return path.resolve(app.getPath('userData'), 'chains/mainnet') -} - const initCkb = async () => { logger.info('CKB:\tInitializing node...') return new Promise((resolve, reject) => { - if (fs.existsSync(path.join(ckbDataPath(), 'ckb.toml'))) { + if (fs.existsSync(path.join(SettingsService.getInstance().ckbDataPath, 'ckb.toml'))) { logger.log('CKB:\tinit: config file detected, skip ckb init.') return resolve() } - const initCmd = spawn(ckbBinary(), ['init', '--chain', 'mainnet', '-C', ckbDataPath()]) + const initCmd = spawn(ckbBinary(), ['init', '--chain', 'mainnet', '-C', SettingsService.getInstance().ckbDataPath]) initCmd.stderr.on('data', data => { logger.error('CKB:\tinit fail:', data.toString()) }) @@ -71,7 +68,7 @@ export const startCkbNode = async () => { await initCkb() logger.info('CKB:\tstarting node...') - const options = ['run', '-C', ckbDataPath()] + const options = ['run', '-C', SettingsService.getInstance().ckbDataPath] if (app.isPackaged && process.env.CKB_NODE_ASSUME_VALID_TARGET) { options.push('--assume-valid-target', process.env.CKB_NODE_ASSUME_VALID_TARGET) } @@ -130,6 +127,6 @@ export const stopCkbNode = () => { */ export const clearCkbNodeCache = async () => { await stopCkbNode() - fs.rmSync(ckbDataPath(), { recursive: true, force: true }) + fs.rmSync(SettingsService.getInstance().ckbDataPath, { recursive: true, force: true }) await startCkbNode() } diff --git a/packages/neuron-wallet/src/services/indexer.ts b/packages/neuron-wallet/src/services/indexer.ts index 7b579c9c48..f2095a4661 100644 --- a/packages/neuron-wallet/src/services/indexer.ts +++ b/packages/neuron-wallet/src/services/indexer.ts @@ -12,6 +12,7 @@ import NetworksService from './networks' import CommonUtils from 'utils/common' import { resetSyncTask } from 'block-sync-renderer' import { clean as cleanChain } from 'database/chain' +import SettingsService from './settings' const platform = (): string => { switch (process.platform) { @@ -186,6 +187,11 @@ export default class IndexerService { #getDataPath = (network: Network): string => { - return path.resolve(env.fileBasePath, IndexerService.indexerDataFolder, 'data', `${network.genesisHash}`) + let indexerDataPath = SettingsService.getInstance().indexerDataPath + if (!indexerDataPath) { + indexerDataPath = path.resolve(env.fileBasePath, IndexerService.indexerDataFolder, 'data', `${network.genesisHash}`) + SettingsService.getInstance().indexerDataPath = indexerDataPath + } + return indexerDataPath } } diff --git a/packages/neuron-wallet/src/services/monitor/base.ts b/packages/neuron-wallet/src/services/monitor/base.ts index 47aa052f3b..5284c22e0b 100644 --- a/packages/neuron-wallet/src/services/monitor/base.ts +++ b/packages/neuron-wallet/src/services/monitor/base.ts @@ -1,11 +1,13 @@ import logger from 'utils/logger' -import { interval, timer, Subscription, race, from } from 'rxjs' +import { interval, timer, Subscription, race, from, Observable } from 'rxjs' import { map } from 'rxjs/operators' export default abstract class Monitor { - interval: Subscription | null = null + private interval: Observable | null = null - isReStarting: boolean = false + private subcription: Subscription | null = null + + private isReStarting: boolean = false name: string = '' @@ -13,27 +15,38 @@ export default abstract class Monitor { abstract restart(): Promise - startMonitor(intervalTime: number = 10000) { - this.interval = interval(intervalTime).subscribe(async () => { - if (this.isReStarting) { - return - } - const timeout = timer(intervalTime / 2).pipe(map(() => true)) - const isLiving = await race(timeout, from(this.isLiving())).toPromise() - if (!isLiving) { - logger.info(`Monitor: is restarting ${this.name} process`) - this.isReStarting = true - try { - await this.restart() - logger.info(`Monitor: Restarting ${this.name} process success`) - } finally { - this.isReStarting = false - } + abstract stop(): Promise + + monitor = async (intervalTime: number) => { + if (this.isReStarting) { + return + } + const timeout = timer(intervalTime / 2).pipe(map(() => true)) + const isLiving = await race(timeout, from(this.isLiving())).toPromise() + if (!isLiving) { + logger.info(`Monitor: is restarting ${this.name} process`) + this.isReStarting = true + try { + await this.restart() + logger.info(`Monitor: Restarting ${this.name} process success`) + } finally { + this.isReStarting = false } - }) + } + } + + startMonitor(intervalTime: number = 10000, startNow: boolean = false) { + this.interval = interval(intervalTime) + if (startNow) { + this.monitor(intervalTime) + } + if (!this.subcription?.closed) { + this.subcription?.unsubscribe() + } + this.subcription = this.interval.subscribe(async () => this.monitor(intervalTime)) } clearMonitor() { - this.interval?.unsubscribe() + this.subcription?.unsubscribe() } } diff --git a/packages/neuron-wallet/src/services/monitor/ckb-indexer-monitor.ts b/packages/neuron-wallet/src/services/monitor/ckb-indexer-monitor.ts index 669431583d..6c4b433cfc 100644 --- a/packages/neuron-wallet/src/services/monitor/ckb-indexer-monitor.ts +++ b/packages/neuron-wallet/src/services/monitor/ckb-indexer-monitor.ts @@ -20,5 +20,9 @@ export default class CkbIndexerMonitor extends BaseMonitor { await resetSyncTask() } + async stop(): Promise { + await resetSyncTask(false) + } + name: string = 'ckb-indexer' } diff --git a/packages/neuron-wallet/src/services/monitor/ckb-monitor.ts b/packages/neuron-wallet/src/services/monitor/ckb-monitor.ts index 23bc6b7050..eb3fccee85 100644 --- a/packages/neuron-wallet/src/services/monitor/ckb-monitor.ts +++ b/packages/neuron-wallet/src/services/monitor/ckb-monitor.ts @@ -1,3 +1,4 @@ +import { stopCkbNode } from 'services/ckb-runner' import NodeService from '../node' import BaseMonitor from './base' @@ -10,5 +11,9 @@ export default class CkbMonitor extends BaseMonitor { return NodeService.getInstance().startNode() } + async stop(): Promise { + await stopCkbNode() + } + name: string = 'ckb' } diff --git a/packages/neuron-wallet/src/services/monitor/index.ts b/packages/neuron-wallet/src/services/monitor/index.ts index f717e94980..89cd79621f 100644 --- a/packages/neuron-wallet/src/services/monitor/index.ts +++ b/packages/neuron-wallet/src/services/monitor/index.ts @@ -2,9 +2,26 @@ import Base from './base' import CkbIndexerMonitor from './ckb-indexer-monitor' import CkbMonitor from './ckb-monitor' -export default function startMonitor() { - const monitors = [new CkbIndexerMonitor(), new CkbMonitor()] - monitors.forEach((v: Base) => { - v.startMonitor() - }) +const monitors: Base[] = [] + +export default function startMonitor(name?: string, startNow?: boolean) { + if (!monitors.length) { + monitors.push(new CkbIndexerMonitor(), new CkbMonitor()) + } + monitors + .filter(v => !name || v.name === name) + .forEach((v: Base) => { + v.startMonitor(undefined, startNow) + }) +} + +export async function stopMonitor(name?: string) { + await Promise.all( + monitors + .filter(v => !name || v.name === name) + .map(v => { + v.clearMonitor() + return v.stop() + }) + ) } diff --git a/packages/neuron-wallet/src/services/settings.ts b/packages/neuron-wallet/src/services/settings.ts index 9ad562e9b8..2583f5fc0d 100644 --- a/packages/neuron-wallet/src/services/settings.ts +++ b/packages/neuron-wallet/src/services/settings.ts @@ -3,6 +3,9 @@ import env from 'env' import Store from 'models/store' import { changeLanguage } from 'locales/i18n' import { updateApplicationMenu } from 'controllers/app/menu' +import path from 'path' + +const { app } = env export const locales = ['zh', 'zh-TW', 'en', 'en-US'] as const export type Locale = typeof locales[number] @@ -31,8 +34,34 @@ export default class SettingsService extends Store { } } + get indexerDataPath() { + return this.readSync('indexerDataPath') + } + + set indexerDataPath(dataPath: string) { + this.writeSync('indexerDataPath', dataPath) + } + + get ckbDataPath() { + return this.readSync('ckbDataPath') + } + + set ckbDataPath(dataPath: string) { + this.writeSync('ckbDataPath', dataPath) + } + constructor() { - super('', 'settings.json', JSON.stringify({ locale: env.app.getLocale() })) + super( + '', + 'settings.json', + JSON.stringify({ + locale: app.getLocale(), + ckbDataPath: path.resolve(app.getPath('userData'), 'chains/mainnet') + }) + ) + if (!this.ckbDataPath) { + this.ckbDataPath = path.resolve(app.getPath('userData'), 'chains/mainnet') + } } private onLocaleChanged = (lng: Locale) => { diff --git a/packages/neuron-wallet/tests/block-sync-renderer/tx-status-listener.intg.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/tx-status-listener.intg.test.ts index 7875379866..b2386e6f3b 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/tx-status-listener.intg.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/tx-status-listener.intg.test.ts @@ -39,7 +39,9 @@ jest.doMock('rxjs', () => { __esModule: true, interval: stubbedRxJsIntervalFn, ReplaySubject: jest.fn(), - Subject: jest.fn(), + Subject: jest.fn().mockReturnValue({ + pipe: jest.fn().mockReturnValue({ subscribe: jest.fn() }) + }), BehaviorSubject: jest.fn(), } }); diff --git a/packages/neuron-wallet/tests/controllers/asset-account.test.ts b/packages/neuron-wallet/tests/controllers/asset-account.test.ts index d9169d952f..b0d8a6027d 100644 --- a/packages/neuron-wallet/tests/controllers/asset-account.test.ts +++ b/packages/neuron-wallet/tests/controllers/asset-account.test.ts @@ -22,6 +22,14 @@ const resetMocks = () => { stubbedAssetAccountServiceDestoryAssetAccount.mockReset() } +jest.mock('../../src/services/settings', () => { + return { + getInstance() { + return {} + } + } +}) + describe('AssetAccountController', () => { let assetAccountController: any; let AssetAccountController: any; diff --git a/packages/neuron-wallet/tests/controllers/export-debug.test.ts b/packages/neuron-wallet/tests/controllers/export-debug.test.ts index 0b8a733e1c..7b361a65b7 100644 --- a/packages/neuron-wallet/tests/controllers/export-debug.test.ts +++ b/packages/neuron-wallet/tests/controllers/export-debug.test.ts @@ -33,7 +33,10 @@ jest.mock('../../src/services/addresses', () => { jest.mock('fs', () => { return { - createWriteStream: () => null + createWriteStream: () => null, + readFileSync: () => JSON.stringify({}), + writeFileSync: () => jest.fn(), + existsSync: () => jest.fn() } }) diff --git a/packages/neuron-wallet/tests/controllers/multisig.test.ts b/packages/neuron-wallet/tests/controllers/multisig.test.ts index 8aa8af7e96..5c56d98cba 100644 --- a/packages/neuron-wallet/tests/controllers/multisig.test.ts +++ b/packages/neuron-wallet/tests/controllers/multisig.test.ts @@ -63,7 +63,7 @@ const isMainnetMock = jest.fn().mockReturnValue(false) jest.mock('../../src/services/networks', () => ({ getInstance: () => ({ - isMainnet: isMainnetMock + isMainnet: () => isMainnetMock() }) })) diff --git a/packages/neuron-wallet/tests/services/ckb-runner.test.ts b/packages/neuron-wallet/tests/services/ckb-runner.test.ts index 0287219d0b..d9d200317a 100644 --- a/packages/neuron-wallet/tests/services/ckb-runner.test.ts +++ b/packages/neuron-wallet/tests/services/ckb-runner.test.ts @@ -55,6 +55,13 @@ jest.doMock('utils/logger', () => { jest.doMock('process', () => { return stubbedProcess }) +jest.mock('../../src/services/settings', () => ({ + getInstance() { + return { + ckbDataPath: '/chains/mainnet' + } + } +})) const { startCkbNode, stopCkbNode, getLookingValidTargetStatus } = require('../../src/services/ckb-runner') describe('ckb runner', () => { diff --git a/packages/neuron-wallet/tests/services/monitor.test.ts b/packages/neuron-wallet/tests/services/monitor.test.ts index af8ab977ca..388fb05833 100644 --- a/packages/neuron-wallet/tests/services/monitor.test.ts +++ b/packages/neuron-wallet/tests/services/monitor.test.ts @@ -1,6 +1,7 @@ import Monitor from '../../src/services/monitor/base' import CkbMonitor from '../../src/services/monitor/ckb-monitor' import CkbIndexerMonitor from '../../src/services/monitor/ckb-indexer-monitor' +import startMonitor, { stopMonitor } from '../../src/services/monitor/index' const isDefaultCKBNeedRestartMock = jest.fn().mockResolvedValue(true) const startNodeMock = jest.fn() @@ -14,6 +15,11 @@ jest.mock('../../src/services/node', () => ({ } })) +const stopCkbNodeMock = jest.fn() +jest.mock('../../src/services/ckb-runner', () => ({ + stopCkbNode: () => stopCkbNodeMock() +})) + describe('ckb monitor', () => { const monitor = new CkbMonitor() it('is living', async () => { @@ -24,6 +30,10 @@ describe('ckb monitor', () => { await monitor.restart() expect(startNodeMock).toHaveBeenCalled() }) + it('stop', async () => { + await monitor.stop() + expect(stopCkbNodeMock).toHaveBeenCalled() + }) }) const rpcRequestMock = jest.fn() @@ -33,7 +43,7 @@ jest.mock('../../src/utils/rpc-request', () => ({ const resetSyncTaskMock = jest.fn() jest.mock('../../src/block-sync-renderer/index', () => ({ - resetSyncTask: () => resetSyncTaskMock() + resetSyncTask: (v: boolean) => resetSyncTaskMock(v) })) describe('ckb indexer monitor', () => { @@ -58,10 +68,15 @@ describe('ckb indexer monitor', () => { await monitor.restart() expect(resetSyncTaskMock).toHaveBeenCalled() }) + it('stop', async () => { + await monitor.stop() + expect(resetSyncTaskMock).toHaveBeenCalledWith(false) + }) }) const isLivingMock = jest.fn() const restartMock = jest.fn() +const stopMock = jest.fn() class MonitorTest extends Monitor { isLiving(): Promise { return isLivingMock() @@ -70,6 +85,10 @@ class MonitorTest extends Monitor { restart(): Promise { return restartMock() } + + stop(): Promise { + return stopMock() + } } function wait(times: number) { @@ -119,5 +138,56 @@ describe('base monitor', () => { expect(restartMock).toHaveBeenCalledTimes(1) monitor.clearMonitor() }) + it('start monitor with first', async () => { + isLivingMock.mockResolvedValue(true).mockResolvedValueOnce(false) + monitor.startMonitor(1000, true) + await wait(500) + expect(isLivingMock).toHaveBeenCalled() + expect(restartMock).toHaveBeenCalled() + monitor.clearMonitor() + }) + it('twice start monitor', async () => { + isLivingMock.mockReset() + isLivingMock.mockResolvedValue(true) + monitor.startMonitor(500) + monitor.startMonitor(500) + await wait(800) + expect(isLivingMock).toHaveBeenCalledTimes(1) + monitor.clearMonitor() + }) }) }) + +describe('monitor index', () => { + beforeEach(() => { + rpcRequestMock.mockReset() + stopCkbNodeMock.mockReset() + resetSyncTaskMock.mockReset() + }) + it('start ckb monitor', () => { + startMonitor('ckb', true) + expect(isDefaultCKBNeedRestartMock).toHaveBeenCalled() + stopMonitor('ckb') + }) + it('start indexer monitor', () => { + startMonitor('ckb-indexer', true) + expect(rpcRequestMock).toHaveBeenCalled() + stopMonitor('ckb-indexer') + }) + it('start and stop all', () => { + startMonitor(undefined, true) + expect(isDefaultCKBNeedRestartMock).toHaveBeenCalled() + expect(rpcRequestMock).toHaveBeenCalled() + stopMonitor() + expect(stopCkbNodeMock).toHaveBeenCalled() + expect(resetSyncTaskMock).toHaveBeenCalledWith(false) + }) + it('stop-ckb-monitor', () => { + stopMonitor('ckb') + expect(stopCkbNodeMock).toHaveBeenCalled() + }) + it('stop-ckb-indexer-monitor', () => { + stopMonitor('ckb-indexer') + expect(resetSyncTaskMock).toHaveBeenCalledWith(false) + }) +}) \ No newline at end of file diff --git a/packages/neuron-wallet/tests/services/setting.test.ts b/packages/neuron-wallet/tests/services/setting.test.ts new file mode 100644 index 0000000000..bd560398cc --- /dev/null +++ b/packages/neuron-wallet/tests/services/setting.test.ts @@ -0,0 +1,95 @@ +import SettingsService from '../../src/services/settings' + +const readSyncMock = jest.fn() +const writeSyncMock = jest.fn() +const resolveMock = jest.fn() +const getLocaleMock = jest.fn() +const getPathMock = jest.fn() +const updateApplicationMenuMock = jest.fn() + +function resetMock() { + readSyncMock.mockReset() + writeSyncMock.mockReset() + resolveMock.mockReset() + getLocaleMock.mockReset() + getPathMock.mockReset() + updateApplicationMenuMock.mockReset() +} + +jest.mock('../../src/models/store', () => { + function MockStore() {} + MockStore.prototype.readSync = (field: string) => readSyncMock(field) + MockStore.prototype.writeSync = (field: string, value: any) => writeSyncMock(field, value) + return MockStore +}) + +jest.mock('../../src/controllers/app/menu', () => ({ + updateApplicationMenu: () => updateApplicationMenuMock() +})) + +jest.mock('electron', () => ({ + BrowserWindow: { + getAllWindows: jest.fn().mockReturnValue([]) + } +})) + +jest.mock('path', () => ({ + resolve: () => resolveMock() +})) + +jest.mock('env', () => ({ + app: { + getLocale: () => getLocaleMock(), + getPath: () => getPathMock() + } +})) + +describe('SettingsService', () => { + beforeEach(() => { + resetMock() + }) + + it('constructor no ckbDataPath', () => { + const instance = SettingsService.getInstance() + readSyncMock.mockReturnValue('ckbDataPath') + expect(instance.ckbDataPath).toEqual('ckbDataPath') + }) + + describe('locale', () => { + it('get', () => { + SettingsService.getInstance().locale + expect(readSyncMock).toBeCalledWith('locale') + }) + it('set', () => { + SettingsService.getInstance().locale = 'zh' + expect(writeSyncMock).toBeCalledWith('locale', 'zh') + expect(updateApplicationMenuMock).toHaveBeenCalled() + }) + it('set exception', () => { + expect(() => SettingsService.getInstance().locale = 'zh11' as any).toThrow(new Error(`Locale zh11 not supported`)) + }) + }) + + + describe('indexer-path', () => { + it('get', () => { + SettingsService.getInstance().indexerDataPath + expect(readSyncMock).toBeCalledWith('indexerDataPath') + }) + it('set', () => { + SettingsService.getInstance().indexerDataPath = 'indexerDataPath' + expect(writeSyncMock).toBeCalledWith('indexerDataPath', 'indexerDataPath') + }) + }) + + describe('ckb-node-path', () => { + it('get', () => { + SettingsService.getInstance().ckbDataPath + expect(readSyncMock).toBeCalledWith('ckbDataPath') + }) + it('set', () => { + SettingsService.getInstance().ckbDataPath = 'ckbDataPath' + expect(writeSyncMock).toBeCalledWith('ckbDataPath', 'ckbDataPath') + }) + }) +}) \ No newline at end of file From 94718bb9d146b2fc71eccc098077c142e6421135 Mon Sep 17 00:00:00 2001 From: yanguoyu <841185308@qq.com> Date: Wed, 29 Jun 2022 08:58:08 +0800 Subject: [PATCH 2/8] fix: remove mistake pull --- packages/neuron-wallet/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index bbb1d7626b..8b8b69cd08 100644 --- a/packages/neuron-wallet/package.json +++ b/packages/neuron-wallet/package.json @@ -23,7 +23,7 @@ "start:dev": "yarn run build && electron .", "start:debug": "yarn run build && electron --inspect=5858 .", "clean": "rimraf dist/*", - "test": "jest --runInBand --collect-coverage --forceExit packages/neuron-wallet/tests/services/setting.test.ts", + "test": "jest --runInBand --collect-coverage --forceExit", "test:watch": "jest --watch", "lint": "eslint --fix --ext .ts,.js src", "precommit": "lint-staged", From d4999de26fe07d622d0bcb1e57e8af60682c6f95 Mon Sep 17 00:00:00 2001 From: yanguoyu <841185308@qq.com> Date: Wed, 29 Jun 2022 08:59:51 +0800 Subject: [PATCH 3/8] fix: Add endline --- packages/neuron-wallet/tests/services/monitor.test.ts | 2 +- packages/neuron-wallet/tests/services/setting.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/neuron-wallet/tests/services/monitor.test.ts b/packages/neuron-wallet/tests/services/monitor.test.ts index 388fb05833..96003a3a26 100644 --- a/packages/neuron-wallet/tests/services/monitor.test.ts +++ b/packages/neuron-wallet/tests/services/monitor.test.ts @@ -190,4 +190,4 @@ describe('monitor index', () => { stopMonitor('ckb-indexer') expect(resetSyncTaskMock).toHaveBeenCalledWith(false) }) -}) \ No newline at end of file +}) diff --git a/packages/neuron-wallet/tests/services/setting.test.ts b/packages/neuron-wallet/tests/services/setting.test.ts index bd560398cc..4e3a48ff3c 100644 --- a/packages/neuron-wallet/tests/services/setting.test.ts +++ b/packages/neuron-wallet/tests/services/setting.test.ts @@ -92,4 +92,4 @@ describe('SettingsService', () => { expect(writeSyncMock).toBeCalledWith('ckbDataPath', 'ckbDataPath') }) }) -}) \ No newline at end of file +}) From 0c8c47c489936a33bbabad2ba731f775a6a491a5 Mon Sep 17 00:00:00 2001 From: yanguoyu <841185308@qq.com> Date: Wed, 29 Jun 2022 14:24:29 +0800 Subject: [PATCH 4/8] fix: Fix ci test case failed. --- .../tests/block-sync-renderer/index/resetSyncTask.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/neuron-wallet/tests/block-sync-renderer/index/resetSyncTask.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/index/resetSyncTask.test.ts index 9b90bd5a58..5639bd7cb6 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/index/resetSyncTask.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/index/resetSyncTask.test.ts @@ -1,8 +1,9 @@ describe(`Reset sync task`, () => { const stubbedmaintainAddressesIfNecessary = jest.fn() const stubbedSleep = jest.fn() + const stubbedTimeout = jest.fn().mockReturnValue(Promise.resolve(10)) jest.doMock('services/wallets', () => ({ getInstance: () => ({ maintainAddressesIfNecessary: stubbedmaintainAddressesIfNecessary }) })) - jest.doMock('utils/common', () => ({ sleep: stubbedSleep })) + jest.doMock('utils/common', () => ({ sleep: stubbedSleep, timeout: stubbedTimeout })) const blockSyncRenderer = require('block-sync-renderer') const spyCreateBlockSyncTask = jest.spyOn(blockSyncRenderer, 'createBlockSyncTask').mockImplementation(() => jest.fn()) From 921d84e513bcc61a0e6e510283f75c73095ffaf8 Mon Sep 17 00:00:00 2001 From: yanguoyu <841185308@qq.com> Date: Fri, 1 Jul 2022 15:35:10 +0800 Subject: [PATCH 5/8] fix: Add end line and use void not undefined with parameter --- .../neuron-ui/src/components/DataSetting/index.module.scss | 2 +- packages/neuron-ui/src/services/remote/app.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/neuron-ui/src/components/DataSetting/index.module.scss b/packages/neuron-ui/src/components/DataSetting/index.module.scss index 78f051afad..1fb9a721b4 100644 --- a/packages/neuron-ui/src/components/DataSetting/index.module.scss +++ b/packages/neuron-ui/src/components/DataSetting/index.module.scss @@ -54,4 +54,4 @@ display: flex; justify-content: space-between; } -} \ No newline at end of file +} diff --git a/packages/neuron-ui/src/services/remote/app.ts b/packages/neuron-ui/src/services/remote/app.ts index 6b22c96a06..76340290cd 100644 --- a/packages/neuron-ui/src/services/remote/app.ts +++ b/packages/neuron-ui/src/services/remote/app.ts @@ -17,9 +17,9 @@ export const requestOpenInExplorer = remoteApi('handle-view-error') export const showSettings = remoteApi('show-settings') export const setLocale = remoteApi('set-locale') -export const getCkbNodeDataPath = remoteApi('get-ckb-node-data-path') +export const getCkbNodeDataPath = remoteApi('get-ckb-node-data-path') export const setCkbNodeDataPath = remoteApi('set-ckb-node-data-path') -export const getIndexerDataPath = remoteApi('get-indexer-data-path') +export const getIndexerDataPath = remoteApi('get-indexer-data-path') export const setIndexerDataPath = remoteApi('set-indexer-data-path') export const stopProcessMonitor = remoteApi<'ckb' | 'ckb-indexer'>('stop-process-monitor') export const startProcessMonitor = remoteApi<'ckb' | 'ckb-indexer'>('start-process-monitor') From 6b5f26a99704114dcba341446652280c770b0755 Mon Sep 17 00:00:00 2001 From: yanguoyu <841185308@qq.com> Date: Fri, 1 Jul 2022 16:18:18 +0800 Subject: [PATCH 6/8] fix: Fix windows testcase --- .../neuron-wallet/tests/services/ckb-runner.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/neuron-wallet/tests/services/ckb-runner.test.ts b/packages/neuron-wallet/tests/services/ckb-runner.test.ts index d9d200317a..bf74450e83 100644 --- a/packages/neuron-wallet/tests/services/ckb-runner.test.ts +++ b/packages/neuron-wallet/tests/services/ckb-runner.test.ts @@ -55,10 +55,12 @@ jest.doMock('utils/logger', () => { jest.doMock('process', () => { return stubbedProcess }) + +const ckbDataPath = '/chains/mainnet' jest.mock('../../src/services/settings', () => ({ getInstance() { return { - ckbDataPath: '/chains/mainnet' + ckbDataPath } } })) @@ -94,13 +96,13 @@ describe('ckb runner', () => { '--chain', 'mainnet', '-C', - expect.stringContaining(path.join('chains', 'mainnet')) + ckbDataPath ]) }) it('runs ckb binary', () => { expect(stubbedSpawn).toHaveBeenCalledWith( expect.stringContaining(path.join(platformPath, 'ckb')), - ['run', '-C', expect.stringContaining(path.join('chains', 'mainnet'))], + ['run', '-C', ckbDataPath], { stdio: ['ignore', 'pipe', 'pipe'] } ) }) @@ -123,13 +125,13 @@ describe('ckb runner', () => { '--chain', 'mainnet', '-C', - expect.stringContaining(path.join('chains', 'mainnet')) + ckbDataPath ]) }) it('runs ckb binary', () => { expect(stubbedSpawn).toHaveBeenCalledWith( expect.stringContaining(path.join(platformPath, 'ckb')), - ['run', '-C', expect.stringContaining(path.join('chains', 'mainnet'))], + ['run', '-C', ckbDataPath], { stdio: ['ignore', 'pipe', 'pipe'] } ) }) From 3b6e9db935d02e054e28790ff0c317c4e48b719d Mon Sep 17 00:00:00 2001 From: yanguoyu <841185308@qq.com> Date: Mon, 4 Jul 2022 08:55:09 +0800 Subject: [PATCH 7/8] feat: Add open data path by electron API --- .../components/DataSetting/index.module.scss | 5 +++++ .../src/components/DataSetting/index.tsx | 18 ++++++++++++++---- .../neuron-ui/src/widgets/Icons/OpenFolder.svg | 2 ++ packages/neuron-ui/src/widgets/Icons/icon.tsx | 2 ++ 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 packages/neuron-ui/src/widgets/Icons/OpenFolder.svg diff --git a/packages/neuron-ui/src/components/DataSetting/index.module.scss b/packages/neuron-ui/src/components/DataSetting/index.module.scss index 1fb9a721b4..6a67de076d 100644 --- a/packages/neuron-ui/src/components/DataSetting/index.module.scss +++ b/packages/neuron-ui/src/components/DataSetting/index.module.scss @@ -55,3 +55,8 @@ justify-content: space-between; } } + +.path { + display: flex; + align-items: center; +} \ No newline at end of file diff --git a/packages/neuron-ui/src/components/DataSetting/index.tsx b/packages/neuron-ui/src/components/DataSetting/index.tsx index 5836e7fd36..eaf994a914 100644 --- a/packages/neuron-ui/src/components/DataSetting/index.tsx +++ b/packages/neuron-ui/src/components/DataSetting/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from 'widgets/Button' import ClearCache from 'components/ClearCache' @@ -6,6 +6,8 @@ import { useDispatch } from 'states' import { getCkbNodeDataPath, getIndexerDataPath, setCkbNodeDataPath, setIndexerDataPath } from 'services/remote' import { ReactComponent as Attention } from 'widgets/Icons/ExperimentalAttention.svg' import CopyZone from 'widgets/CopyZone' +import { OpenFolder } from 'widgets/Icons/icon' +import { shell } from 'electron' import { useDataPath } from './hooks' import styles from './index.module.scss' @@ -44,12 +46,20 @@ const SetItem = ({ type }: { type: keyof typeof itemProps }) => { props.setPath, props.type ) + const openPath = useCallback(() => { + if (prevPath) { + shell.openPath(prevPath!) + } + }, [prevPath]) return ( <>
{t(`settings.data.${props.titleI18nKey}`)}:
- - {prevPath} - +
+ + {prevPath} + + +