diff --git a/public/locales/en/translations.json b/public/locales/en/translations.json index c586d9da7..ca06c9687 100644 --- a/public/locales/en/translations.json +++ b/public/locales/en/translations.json @@ -494,6 +494,12 @@ "logout": "Sync Stopped", "message": { "canceled": "Canceled old Sync Watcher" + }, + "estimated_time": { + "started_at": "Sync started at: ", + "spent_time": "Spent time: ", + "left_time": "Left time: ", + "estimating": "Estimating..." } }, "sum": "Sum", diff --git a/src/components/Header/SyncMode/SyncMode.container.js b/src/components/Header/SyncMode/SyncMode.container.js index 9afff44a8..8738f5091 100644 --- a/src/components/Header/SyncMode/SyncMode.container.js +++ b/src/components/Header/SyncMode/SyncMode.container.js @@ -3,13 +3,14 @@ import { connect } from 'react-redux' import { withTranslation } from 'react-i18next' import { startSyncNow, stopSyncNow } from 'state/sync/actions' -import { getSyncProgress, getIsSyncing } from 'state/sync/selectors' +import { getSyncProgress, getIsSyncing, getEstimatedSyncTime } from 'state/sync/selectors' import SyncMode from './SyncMode' const mapStateToProps = state => ({ isSyncing: getIsSyncing(state), syncProgress: getSyncProgress(state), + estimatedSyncTime: getEstimatedSyncTime(state), }) const mapDispatchToProps = { diff --git a/src/components/Header/SyncMode/SyncMode.helpers.js b/src/components/Header/SyncMode/SyncMode.helpers.js index e5d37b607..f6b83a96c 100644 --- a/src/components/Header/SyncMode/SyncMode.helpers.js +++ b/src/components/Header/SyncMode/SyncMode.helpers.js @@ -1,7 +1,33 @@ import React from 'react' import { Spinner } from '@blueprintjs/core' +import _isNull from 'lodash/isNull' import Icon from 'icons' +import { getFormattedTime } from 'utils/dates' + +const getEstimatedSyncTime = ({ + leftTime = null, + spentTime = null, + syncStartedAt = null, +}, t) => { + const start = _isNull(syncStartedAt) + ? t('sync.estimated_time.estimating') + : getFormattedTime(syncStartedAt, 'MMMM DD, YYYY HH:mm') + + const spent = _isNull(spentTime) + ? t('sync.estimated_time.estimating') + : getFormattedTime(spentTime, 'mm:ss') + + const left = _isNull(leftTime) + ? t('sync.estimated_time.estimating') + : getFormattedTime(leftTime, 'mm:ss') + + return { + start, + spent, + left, + } +} export const getSyncTitle = (isSyncing) => ( isSyncing @@ -9,11 +35,30 @@ export const getSyncTitle = (isSyncing) => ( : 'sync.start' ) -export const getSyncTooltipMessage = (isSyncing) => ( - isSyncing - ? 'sync.insync_tooltip' - : 'sync.start_sync_tooltip' -) +export const getSyncTooltipContent = (t, isSyncing, estimatedSyncTime) => { + const { start, spent, left } = getEstimatedSyncTime(estimatedSyncTime, t) + + if (isSyncing) { + return ( + <> +

{t('sync.insync_tooltip')}

+

+ {t('sync.estimated_time.started_at')} + {start} +

+

+ {t('sync.estimated_time.spent_time')} + {spent} +

+

+ {t('sync.estimated_time.left_time')} + {left} +

+ + ) + } + return <>{t('sync.start_sync_tooltip')} +} export const getSyncIcon = (isSyncing, syncProgress) => { if (isSyncing) { @@ -32,5 +77,5 @@ export const getSyncIcon = (isSyncing, syncProgress) => { export default { getSyncIcon, getSyncTitle, - getSyncTooltipMessage, + getSyncTooltipContent, } diff --git a/src/components/Header/SyncMode/SyncMode.js b/src/components/Header/SyncMode/SyncMode.js index 892c57acf..cfed143d6 100644 --- a/src/components/Header/SyncMode/SyncMode.js +++ b/src/components/Header/SyncMode/SyncMode.js @@ -10,7 +10,7 @@ import config from 'config' import { getSyncIcon, getSyncTitle, - getSyncTooltipMessage, + getSyncTooltipContent, } from './SyncMode.helpers' const SyncMode = ({ @@ -19,6 +19,7 @@ const SyncMode = ({ stopSyncNow, syncProgress, startSyncNow, + estimatedSyncTime, }) => { const handleSync = () => { if (isSyncing) { @@ -38,8 +39,8 @@ const SyncMode = ({ <>
@@ -60,11 +61,16 @@ SyncMode.propTypes = { startSyncNow: PropTypes.func, isSyncing: PropTypes.bool.isRequired, syncProgress: PropTypes.number.isRequired, + estimatedSyncTime: PropTypes.shape({ + leftTime: PropTypes.number, + spentTime: PropTypes.number, + syncStartedAt: PropTypes.number, + }).isRequired, } SyncMode.defaultProps = { - startSyncNow: () => {}, stopSyncNow: () => {}, + startSyncNow: () => {}, } export default memo(SyncMode) diff --git a/src/state/sync/actions.js b/src/state/sync/actions.js index 47cac0022..dcd4be67a 100644 --- a/src/state/sync/actions.js +++ b/src/state/sync/actions.js @@ -88,6 +88,13 @@ export function setSyncProgress(payload) { } } +export function setEstimatedTime(payload) { + return { + type: types.SET_ESTIMATED_TIME, + payload, + } +} + /** * Create an action to set preferences. */ @@ -196,6 +203,7 @@ export default { setSyncMode, switchSyncMode, setSyncProgress, + setEstimatedTime, setIsSyncing, setSyncPref, startSyncing, diff --git a/src/state/sync/constants.js b/src/state/sync/constants.js index 70e40bbf9..1d9b01edc 100644 --- a/src/state/sync/constants.js +++ b/src/state/sync/constants.js @@ -12,6 +12,7 @@ export default { FORCE_OFFLINE: 'BITFINEX/SYNC/OFFLINE', SET_PROGRESS: 'BITFINEX/SYNC/PROGRESS/SET', SET_PREF: 'BITFINEX/SYNC/PREF/SET', + SET_ESTIMATED_TIME: 'BITFINEX/SYNC/ESTIMATED_TIME/SET', EDIT_PUBLIC_TRADES_PREF: 'BITFINEX/SYNC/PREF/EDIT/PUBLIC_TRADES', EDIT_PUBLIC_FUNDING_PREF: 'BITFINEX/SYNC/PREF/EDIT/PUBLIC_FUNDING', diff --git a/src/state/sync/reducer.js b/src/state/sync/reducer.js index dc0a987d5..f3cf0b21c 100644 --- a/src/state/sync/reducer.js +++ b/src/state/sync/reducer.js @@ -19,6 +19,7 @@ const initialState = { }, isSyncing: false, progress: 0, + estimatedSyncTime: {}, } export function syncReducer(state = initialState, action) { @@ -114,6 +115,12 @@ export function syncReducer(state = initialState, action) { progress: payload, } } + case types.SET_ESTIMATED_TIME: { + return { + ...state, + estimatedSyncTime: payload, + } + } default: { return state } diff --git a/src/state/sync/saga.js b/src/state/sync/saga.js index 8cddd9ded..13a8f1c9a 100644 --- a/src/state/sync/saga.js +++ b/src/state/sync/saga.js @@ -68,6 +68,7 @@ function* stopSyncNow() { const { result, error } = yield call(syncNowStop) if (result) { yield put(actions.setIsSyncing(false)) + yield put(actions.setEstimatedTime({})) yield put(updateStatus({ id: 'sync.logout' })) } if (error) { @@ -80,6 +81,7 @@ function* stopSyncing() { const { result, error } = yield call(disableSyncMode) if (result) { yield put(actions.setIsSyncing(false)) + yield put(actions.setEstimatedTime({})) yield put(updateStatus({ id: 'sync.stop-sync' })) } if (error) { @@ -156,16 +158,16 @@ function* initQueryMode() { export function* initSync() { yield call(initQueryMode) - const { result: syncProgress } = yield call(fetchSyncProgress) - if (syncProgress === types.SYNC_NOT_STARTED) { + const { result: { progress } } = yield call(fetchSyncProgress) + if (progress === types.SYNC_NOT_STARTED || progress === 100) { yield put(actions.setIsSyncing(false)) yield call(startSyncing) } else { yield put(actions.setIsSyncing(true)) - const isSyncing = Number.isInteger(syncProgress) && syncProgress !== 100 + const isSyncing = Number.isInteger(progress) && progress !== 100 if (isSyncing) { yield put(actions.setSyncPref({ - progress: syncProgress, + progress, isSyncing: true, })) } else { @@ -176,14 +178,15 @@ export function* initSync() { } function* progressUpdate({ payload }) { - const { result } = payload - if (result === types.SYNC_INTERRUPTED) { + const { result: { progress, ...estimatedTimeValues } } = payload + if (progress === types.SYNC_INTERRUPTED) { yield put(actions.setIsSyncing(false)) } else { - const progress = Number.isInteger(result) - ? result + const syncProgress = Number.isInteger(progress) + ? progress : 0 - yield put(actions.setSyncProgress(progress)) + yield put(actions.setSyncProgress(syncProgress)) + yield put(actions.setEstimatedTime(estimatedTimeValues)) } } @@ -209,14 +212,14 @@ function* requestsRedirectUpdate({ payload }) { function* updateSyncStatus() { const syncMode = yield select(getSyncMode) const isSyncing = yield select(getIsSyncing) - const { result: syncProgress, error: progressError } = yield call(fetchSyncProgress) + const { result: { progress }, error: progressError } = yield call(fetchSyncProgress) - switch (typeof syncProgress) { + switch (typeof progress) { case 'number': - if (!isSyncing && syncProgress !== 100) { + if (!isSyncing && progress !== 100) { yield put(actions.setIsSyncing(true)) } - if (syncProgress === 100) { + if (progress === 100) { yield put(actions.setIsSyncing(false)) yield put(updateStatus({ id: 'sync.sync-done' })) } @@ -228,12 +231,12 @@ function* updateSyncStatus() { break case 'string': default: { - if (syncProgress === 'SYNCHRONIZATION_HAS_NOT_STARTED_YET' - || _includes(syncProgress, 'ServerAvailabilityError')) { + if (progress === 'SYNCHRONIZATION_HAS_NOT_STARTED_YET' + || _includes(progress, 'ServerAvailabilityError')) { return } - yield put(updateSyncErrorStatus(syncProgress)) + yield put(updateSyncErrorStatus(progress)) yield put(actions.stopSyncing()) } } diff --git a/src/state/sync/selectors.js b/src/state/sync/selectors.js index 5168fe191..61669071b 100644 --- a/src/state/sync/selectors.js +++ b/src/state/sync/selectors.js @@ -4,6 +4,7 @@ const getSyncConf = state => getSync(state).config || {} export const getSyncMode = state => getSync(state).syncMode || false export const getSyncProgress = state => getSync(state).progress || 0 export const getIsSyncing = state => getSync(state).isSyncing || false +export const getEstimatedSyncTime = state => getSync(state)?.estimatedSyncTime ?? {} export const getPublicTradesPref = state => getSyncConf(state).publicTradesConf || {} export const getPublicTradesStartTime = state => getPublicTradesPref(state).startTime @@ -22,7 +23,9 @@ export const getStatusMessagesConf = state => getSyncConf(state).statusMessagesC export default { getSyncMode, + getSyncProgress, getIsSyncing, + getEstimatedSyncTime, getPublicTradesPref, getPublicTradesStartTime, getPublicTradesPairs, diff --git a/src/utils/dates.js b/src/utils/dates.js index c987f3c9a..5d983c42c 100644 --- a/src/utils/dates.js +++ b/src/utils/dates.js @@ -8,7 +8,10 @@ export const makeDate = (action) => { return returnVal.getTime() } +export const getFormattedTime = (time, format) => moment(time).format(format) + export default { getFormattedDate, + getFormattedTime, makeDate, }