From df095c2829b4c68524067126125756295bef416d Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 30 Oct 2019 15:53:52 +0100 Subject: [PATCH] Staking updates (#1815) * Staking updates * Bump API * So currentElected first in next up * Linting * Remove unused state entries --- package.json | 6 +- packages/app-accounts/src/modals/Create.tsx | 2 +- packages/app-accounts/src/modals/Qr.tsx | 7 +- .../app-address-book/src/modals/Create.tsx | 2 +- packages/app-contracts/package.json | 2 +- .../app-staking/src/Actions/Account/index.tsx | 71 +++------ packages/app-staking/src/Overview/Address.tsx | 93 +++++++---- .../app-staking/src/Overview/CurrentList.tsx | 16 +- packages/app-staking/src/Overview/index.tsx | 13 +- packages/app-staking/src/index.tsx | 11 +- packages/app-staking/src/types.ts | 3 +- packages/apps/src/Apps.tsx | 20 +++ packages/apps/src/Content/index.tsx | 23 +-- packages/react-api/package.json | 2 +- packages/react-components/src/Badge.tsx | 104 +++++++++++++ .../react-components/src/OnlineStatus.tsx | 145 ++++-------------- packages/react-components/src/index.tsx | 1 + yarn.lock | 90 +++++------ 18 files changed, 330 insertions(+), 281 deletions(-) create mode 100644 packages/react-components/src/Badge.tsx diff --git a/package.json b/package.json index 2df4fa7f9f..442a1f0ffc 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "packages/*" ], "resolutions": { - "@polkadot/api": "^0.96.0-beta.14", - "@polkadot/api-contract": "^0.96.0-beta.14", + "@polkadot/api": "^0.96.0-beta.15", + "@polkadot/api-contract": "^0.96.0-beta.15", "@polkadot/keyring": "^1.6.1", - "@polkadot/types": "^0.96.0-beta.14", + "@polkadot/types": "^0.96.0-beta.15", "@polkadot/util": "^1.6.1", "@polkadot/util-crypto": "^1.6.1", "babel-core": "^7.0.0-bridge.0", diff --git a/packages/app-accounts/src/modals/Create.tsx b/packages/app-accounts/src/modals/Create.tsx index 9f1540fb4a..df1aea4b81 100644 --- a/packages/app-accounts/src/modals/Create.tsx +++ b/packages/app-accounts/src/modals/Create.tsx @@ -142,7 +142,7 @@ function Create ({ className, onClose, onStatusChange, seed: propsSeed, t, type: setAddress(generateSeed(null, derivePath, newSeedType, pairType)); } }; - const _onChangeName = (name: string): void => setName({ isNameValid: name.trim().length !== 0, name }); + const _onChangeName = (name: string): void => setName({ isNameValid: !!name.trim(), name }); const _toggleConfirmation = (): void => setIsConfirmationOpen(!isConfirmationOpen); const _onCommit = (): void => { diff --git a/packages/app-accounts/src/modals/Qr.tsx b/packages/app-accounts/src/modals/Qr.tsx index 92eadc7790..0174af3754 100644 --- a/packages/app-accounts/src/modals/Qr.tsx +++ b/packages/app-accounts/src/modals/Qr.tsx @@ -23,11 +23,10 @@ interface Props extends I18nProps, ModalProps { } function QrModal ({ className, onClose, onStatusChange, t }: Props): React.ReactElement { - const [name, setName] = useState(''); + const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' }); const [scanned, setScanned] = useState(null); - const isNameValid = !!name; - const _onNameChange = (name: string): void => setName(name.trim()); + const _onNameChange = (name: string): void => setName({ isNameValid: !!name.trim(), name }); const _onSave = (): void => { if (!scanned || !isNameValid) { return; @@ -35,7 +34,7 @@ function QrModal ({ className, onClose, onStatusChange, t }: Props): React.React const { address, genesisHash } = scanned; - keyring.addExternal(address, { genesisHash, name }); + keyring.addExternal(address, { genesisHash, name: name.trim() }); InputAddress.setLastValue('account', address); onStatusChange({ diff --git a/packages/app-address-book/src/modals/Create.tsx b/packages/app-address-book/src/modals/Create.tsx index 968c546d4e..5c4570a834 100644 --- a/packages/app-address-book/src/modals/Create.tsx +++ b/packages/app-address-book/src/modals/Create.tsx @@ -40,7 +40,7 @@ function Create ({ onClose, onStatusChange, t }: Props): React.ReactElement { public render (): React.ReactNode { const { className, isSubstrateV2, t } = this.props; - const { controllerId, hexSessionId, isBondExtraOpen, isInjectOpen, isStashValidating, isUnbondOpen, nominees, sessionIds, stashId } = this.state; + const { controllerId, hexSessionId, isBondExtraOpen, isInjectOpen, isStashValidating, isUnbondOpen, nominees, onlineStatus, sessionIds, stashId } = this.state; if (!stashId) { return null; @@ -146,7 +154,12 @@ class Account extends React.PureComponent { return ( + )} label={t('stash')} type='account' value={stashId} @@ -176,7 +189,16 @@ class Account extends React.PureComponent { {this.renderValidate()}
- {this.renderControllerAccount()} + {controllerId && ( +
+ +
+ )} {!isSubstrateV2 && !!sessionIds.length && (
{ ); } - private renderOnlineStatus (): React.ReactNode { - const { onlineStatus, controllerId } = this.state; - - if (!controllerId || !onlineStatus) { - return null; - } - - return ( - - ); - } - - private renderControllerAccount (): React.ReactNode { - const { t } = this.props; - const { controllerId } = this.state; - - if (!controllerId) { - return null; - } - - return ( -
- -
- - ); - } - private renderNominate (): React.ReactNode { const { stashOptions } = this.props; const { controllerId, isNominateOpen, nominees, stashId } = this.state; diff --git a/packages/app-staking/src/Overview/Address.tsx b/packages/app-staking/src/Overview/Address.tsx index 6c71d18020..a73de71862 100644 --- a/packages/app-staking/src/Overview/Address.tsx +++ b/packages/app-staking/src/Overview/Address.tsx @@ -11,10 +11,10 @@ import BN from 'bn.js'; import React, { useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; import { ApiContext, withCalls, withMulti } from '@polkadot/react-api'; -import { AddressCard, AddressMini, OnlineStatus } from '@polkadot/react-components'; +import { AddressCard, AddressMini, Badge, Icon, OnlineStatus } from '@polkadot/react-components'; import { classes } from '@polkadot/react-components/util'; import keyring from '@polkadot/ui-keyring'; -import { formatBalance, formatNumber } from '@polkadot/util'; +import { formatNumber } from '@polkadot/util'; import { updateOnlineStatus } from '../util'; import translate from '../translate'; @@ -23,6 +23,7 @@ interface Props extends I18nProps { address: string; authorsMap: Record; className?: string; + currentElected: string[]; defaultName: string; filter: ValidatorFilter; lastAuthor?: string; @@ -36,9 +37,11 @@ interface StakingState { balanceOpts: { bonded: boolean | BN[] }; controllerId?: string; hasNominators: boolean; + isNominatorMe: boolean; + isSelected: boolean; nominators: [AccountId, Balance][]; - stashActive: string | null; - stashTotal: string | null; + // stashActive: string | null; + // stashTotal: string | null; sessionId?: string; stashId?: string; } @@ -50,20 +53,21 @@ interface OnlineState { const WITH_VALIDATOR_PREFS = { validatorPayment: true }; -function Address ({ authorsMap, className, defaultName, filter, lastAuthor, points, recentlyOnline, stakingInfo, t, withNominations }: Props): React.ReactElement | null { +function Address ({ address, authorsMap, className, currentElected, defaultName, filter, lastAuthor, points, recentlyOnline, stakingInfo, t, withNominations }: Props): React.ReactElement | null { const { isSubstrateV2 } = useContext(ApiContext); const [extraInfo, setExtraInfo] = useState<[React.ReactNode, React.ReactNode][] | undefined>(); - const [isNominatorMe, seIsNominatorMe] = useState(false); const [{ hasOfflineWarnings, onlineStatus }, setOnlineStatus] = useState({ hasOfflineWarnings: false, onlineStatus: {} }); - const [{ balanceOpts, controllerId, hasNominators, nominators, sessionId, stashId }, setStakingState] = useState({ + const [{ balanceOpts, controllerId, hasNominators, isNominatorMe, isSelected, nominators, sessionId, stashId }, setStakingState] = useState({ balanceOpts: { bonded: true }, hasNominators: false, - nominators: [], - stashActive: null, - stashTotal: null + isNominatorMe: false, + isSelected: false, + nominators: [] + // stashActive: null, + // stashTotal: null }); useEffect((): void => { @@ -78,15 +82,13 @@ function Address ({ authorsMap, className, defaultName, filter, lastAuthor, poin useEffect((): void => { if (stakingInfo) { - const { controllerId, nextSessionId, stakers, stakingLedger, stashId } = stakingInfo; + const { controllerId, nextSessionId, stakers, stashId } = stakingInfo; const nominators = stakers ? stakers.others.map(({ who, value }): [AccountId, Balance] => [who, value.unwrap()]) : []; const myAccounts = keyring.getAccounts().map(({ address }): string => address); + const _stashId = stashId && stashId.toString(); - seIsNominatorMe(nominators.some(([who]): boolean => - myAccounts.includes(who.toString()) - )); setStakingState({ balanceOpts: { bonded: stakers && !stakers.own.isEmpty @@ -95,18 +97,22 @@ function Address ({ authorsMap, className, defaultName, filter, lastAuthor, poin }, controllerId: controllerId && controllerId.toString(), hasNominators: nominators.length !== 0, + isNominatorMe: nominators.some(([who]): boolean => + myAccounts.includes(who.toString()) + ), + isSelected: !!(_stashId && currentElected && currentElected.includes(_stashId)), nominators, sessionId: nextSessionId && nextSessionId.toString(), - stashActive: stakingLedger - ? formatBalance(stakingLedger.active) - : null, - stashId: stashId && stashId.toString(), - stashTotal: stakingLedger - ? formatBalance(stakingLedger.total) - : null + // stashActive: stakingLedger + // ? formatBalance(stakingLedger.active) + // : null, + // stashTotal: stakingLedger + // ? formatBalance(stakingLedger.total) + // : null, + stashId: _stashId }); } - }, [stakingInfo]); + }, [currentElected, stakingInfo]); useEffect((): void => { if (stakingInfo) { @@ -120,14 +126,27 @@ function Address ({ authorsMap, className, defaultName, filter, lastAuthor, poin } }, [recentlyOnline, stakingInfo]); - if (!stashId || (filter === 'hasNominators' && !hasNominators) || + if ((filter === 'hasNominators' && !hasNominators) || (filter === 'noNominators' && hasNominators) || (filter === 'hasWarnings' && !hasOfflineWarnings) || (filter === 'noWarnings' && hasOfflineWarnings) || - (filter === 'iNominated' && !isNominatorMe)) { + (filter === 'iNominated' && !isNominatorMe) || + (filter === 'nextSet' && !isSelected)) { return null; } + if (!stashId) { + return ( + + ); + } + const lastBlockNumber = authorsMap[stashId]; const isAuthor = lastAuthor === stashId; // isDisabled={!!points && points.isEmpty} @@ -156,14 +175,24 @@ function Address ({ authorsMap, className, defaultName, filter, lastAuthor, poin className={className} defaultName={defaultName} extraInfo={extraInfo} - iconInfo={controllerId && onlineStatus && ( - - )} - key={stashId} + iconInfo={ + <> + {controllerId && onlineStatus && ( + + )} + {isSelected && ( + } + isTooltip + type='next' + /> + )} + + } value={stashId} withBalance={balanceOpts} withValidatorPrefs={WITH_VALIDATOR_PREFS} diff --git a/packages/app-staking/src/Overview/CurrentList.tsx b/packages/app-staking/src/Overview/CurrentList.tsx index 96f633b8af..fa91b08b1a 100644 --- a/packages/app-staking/src/Overview/CurrentList.tsx +++ b/packages/app-staking/src/Overview/CurrentList.tsx @@ -15,6 +15,7 @@ import Address from './Address'; interface Props extends I18nProps { authorsMap: Record; + currentElected: string[]; currentValidators: string[]; eraPoints?: EraPoints; lastAuthor?: string; @@ -22,11 +23,12 @@ interface Props extends I18nProps { recentlyOnline?: DerivedHeartbeats; } -function renderColumn (addresses: string[], defaultName: string, withExpanded: boolean, filter: string, { authorsMap, eraPoints, lastAuthor, recentlyOnline }: Props): React.ReactNode { - return addresses.map((address, index): React.ReactNode => ( +function renderColumn (addresses: string[], defaultName: string, withExpanded: boolean, filter: string, without: string[], { authorsMap, currentElected, eraPoints, lastAuthor, recentlyOnline }: Props): React.ReactNode { + return addresses.filter((address): boolean => !without.includes(address)).map((address, index): React.ReactNode => (
{ const [filter, setFilter] = useState('all'); - const { currentValidators, next, t } = props; + const { currentElected, currentValidators, next, t } = props; return (
@@ -57,7 +59,8 @@ function CurrentList (props: Props): React.ReactElement { { text: t('Show only with nominators'), value: 'hasNominators' }, { text: t('Show only without nominators'), value: 'noNominators' }, { text: t('Show only with warnings'), value: 'hasWarnings' }, - { text: t('Show only without warnings'), value: 'noWarnings' } + { text: t('Show only without warnings'), value: 'noWarnings' }, + { text: t('Show only elected for next session'), value: 'nextSet' } ]} value={filter} withLabel={false} @@ -68,13 +71,14 @@ function CurrentList (props: Props): React.ReactElement { emptyText={t('No addresses found')} headerText={t('validators')} > - {renderColumn(currentValidators, t('validator'), true, filter, props)} + {renderColumn(currentValidators, t('validator'), true, filter, [], props)} - {renderColumn(next, t('intention'), false, filter, props)} + {renderColumn(currentElected, t('intention'), false, filter, currentValidators, props)} + {renderColumn(next, t('intention'), false, filter, currentElected || [], props)}
diff --git a/packages/app-staking/src/Overview/index.tsx b/packages/app-staking/src/Overview/index.tsx index 6adaafd5f9..4a311bc93d 100644 --- a/packages/app-staking/src/Overview/index.tsx +++ b/packages/app-staking/src/Overview/index.tsx @@ -18,14 +18,13 @@ interface Props extends BareProps, ComponentProps { eraPoints?: EraPoints; } -export default function Overview (props: Props): React.ReactElement { +export default function Overview ({ allControllers, allStashes, currentElected, currentValidators, eraPoints, recentlyOnline }: Props): React.ReactElement { const { isSubstrateV2 } = useContext(ApiContext); const { byAuthor, lastBlockAuthor, lastBlockNumber } = useContext(BlockAuthorsContext); - const [nextSorted, setNextSorted] = useState([]); - const { allControllers, allStashes, currentValidators, eraPoints, recentlyOnline } = props; + const [next, setNext] = useState([]); useEffect((): void => { - setNextSorted( + setNext( isSubstrateV2 // this is a V2 node currentValidators is a list of stashes ? allStashes.filter((address): boolean => !currentValidators.includes(address)) @@ -38,17 +37,19 @@ export default function Overview (props: Props): React.ReactElement {
diff --git a/packages/app-staking/src/index.tsx b/packages/app-staking/src/index.tsx index 0c8e7bf890..5b947c80da 100644 --- a/packages/app-staking/src/index.tsx +++ b/packages/app-staking/src/index.tsx @@ -27,6 +27,7 @@ interface Props extends AppProps, ApiProps, I18nProps { allAccounts?: SubjectInfo; allStashesAndControllers?: [string[], string[]]; bestNumber?: BlockNumber; + currentElected?: string[]; currentValidators?: string[]; eraPoints?: EraPoints; recentlyOnline?: DerivedHeartbeats; @@ -35,7 +36,7 @@ interface Props extends AppProps, ApiProps, I18nProps { const EMPY_ACCOUNTS: string[] = []; const EMPTY_ALL: [string[], string[]] = [EMPY_ACCOUNTS, EMPY_ACCOUNTS]; -function App ({ allAccounts, allStashesAndControllers: [allStashes, allControllers] = EMPTY_ALL, className, currentValidators = EMPY_ACCOUNTS, basePath, eraPoints, recentlyOnline, t }: Props): React.ReactElement { +function App ({ allAccounts, allStashesAndControllers: [allStashes, allControllers] = EMPTY_ALL, className, currentElected, currentValidators, basePath, eraPoints, recentlyOnline, t }: Props): React.ReactElement { const _renderComponent = (Component: React.ComponentType): () => React.ReactNode => { // eslint-disable-next-line react/display-name return (): React.ReactNode => { @@ -48,7 +49,8 @@ function App ({ allAccounts, allStashesAndControllers: [allStashes, allControlle allAccounts={allAccounts} allControllers={allControllers} allStashes={allStashes} - currentValidators={currentValidators} + currentElected={currentElected || EMPY_ACCOUNTS} + currentValidators={currentValidators || EMPY_ACCOUNTS} eraPoints={eraPoints} recentlyOnline={recentlyOnline} /> @@ -111,6 +113,11 @@ export default withMulti( transform: (validators: AccountId[]): string[] => validators.map((accountId): string => accountId.toString()) }], + ['query.staking.currentElected', { + propName: 'currentElected', + transform: (elected: AccountId[]): string[] => + elected.map((accountId): string => accountId.toString()) + }], ['query.staking.currentEra', { propName: 'currentEra' }], ['query.staking.currentEraPointsEarned', { paramName: 'currentEra', propName: 'eraPoints' }] ), diff --git a/packages/app-staking/src/types.ts b/packages/app-staking/src/types.ts index 9fa94e7782..bb869c8b77 100644 --- a/packages/app-staking/src/types.ts +++ b/packages/app-staking/src/types.ts @@ -14,6 +14,7 @@ export interface ComponentProps { allAccounts?: SubjectInfo; allControllers: string[]; allStashes: string[]; + currentElected: string[]; currentValidators: string[]; eraPoints?: EraPoints; recentlyOnline?: DerivedHeartbeats; @@ -27,4 +28,4 @@ export interface CalculateBalanceProps { export type AccountFilter = 'all' | 'controller' | 'session' | 'stash' | 'unbonded'; -export type ValidatorFilter = 'all' | 'hasNominators' | 'noNominators' | 'hasWarnings' | 'noWarnings' | 'iNominated'; +export type ValidatorFilter = 'all' | 'hasNominators' | 'noNominators' | 'hasWarnings' | 'noWarnings' | 'iNominated' | 'nextSet'; diff --git a/packages/apps/src/Apps.tsx b/packages/apps/src/Apps.tsx index 309b47aa57..7a24b197c1 100644 --- a/packages/apps/src/Apps.tsx +++ b/packages/apps/src/Apps.tsx @@ -11,6 +11,7 @@ import { BareProps as Props } from '@polkadot/react-components/types'; import React, { useState } from 'react'; import store from 'store'; import styled from 'styled-components'; +import { withCalls } from '@polkadot/react-api'; import GlobalStyle from '@polkadot/react-components/styles'; import Signer from '@polkadot/react-signer'; @@ -27,6 +28,24 @@ interface SidebarState { transition: SideBarTransition; } +function Placeholder (): React.ReactElement { + return ( +
+ ); +} + +const WarmUp = withCalls<{}>( + 'derive.accounts.indexes', + 'derive.balances.fees', + 'query.session.validators' + // This are very ineffective queries that + // (a) adds load to the RPC node when activated globally + // (b) is used in additional information (next-up) + // 'derive.staking.all' + // 'derive.staking.controllers' + // 'query.staking.nominators' +)(Placeholder); + function Apps ({ className }: Props): React.ReactElement { const [sidebar, setSidebar] = useState({ isCollapsed: false, @@ -77,6 +96,7 @@ function Apps ({ className }: Props): React.ReactElement {
+ ); } diff --git a/packages/apps/src/Content/index.tsx b/packages/apps/src/Content/index.tsx index e6f2a9dfd0..0e2057f728 100644 --- a/packages/apps/src/Content/index.tsx +++ b/packages/apps/src/Content/index.tsx @@ -3,20 +3,19 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { I18nProps } from '@polkadot/react-components/types'; -import { ApiProps } from '@polkadot/react-api/types'; import React, { useContext } from 'react'; import { withRouter, RouteComponentProps } from 'react-router'; import styled from 'styled-components'; import routing from '@polkadot/apps-routing'; -import { withCalls, withMulti } from '@polkadot/react-api'; +import { ApiContext } from '@polkadot/react-api'; import { StatusContext } from '@polkadot/react-components'; import Status from './Status'; import translate from '../translate'; import NotFound from './NotFound'; -interface Props extends I18nProps, ApiProps, RouteComponentProps {} +interface Props extends I18nProps, RouteComponentProps {} const unknown = { display: { @@ -26,7 +25,8 @@ const unknown = { name: '' }; -function Content ({ isApiConnected, isApiReady, className, location, t }: Props): React.ReactElement { +function Content ({ className, location, t }: Props): React.ReactElement { + const { isApiConnected, isApiReady } = useContext(ApiContext); const { queueAction, stqueue, txqueue } = useContext(StatusContext); const app = location.pathname.slice(1) || ''; const { Component, display: { needsApi }, name } = routing.routes.find((route): boolean => @@ -57,7 +57,7 @@ function Content ({ isApiConnected, isApiReady, className, location, t }: Props) } // React-router needs to be first, otherwise we have blocked updates -export default withMulti( +export default translate( withRouter( styled(Content)` background: #fafafa; @@ -79,18 +79,5 @@ export default withMulti( padding: 1rem 0; } ` - ), - translate, - // These API queries are used in a number of places, warm them up - // to avoid constant un-/re-subscribe on these - withCalls( - 'derive.accounts.indexes', - 'derive.balances.fees', - 'query.session.validators' - // This are very ineffective queries that - // (a) adds load to the RPC node when activated globally - // (b) is used in additional information (next-up) - // 'derive.staking.controllers' - // 'query.staking.nominators' ) ); diff --git a/packages/react-api/package.json b/packages/react-api/package.json index 46aee4e062..d1999fad01 100644 --- a/packages/react-api/package.json +++ b/packages/react-api/package.json @@ -31,7 +31,7 @@ "homepage": "https://github.com/polkadot-js/ui/tree/master/packages/ui-reactive#readme", "dependencies": { "@babel/runtime": "^7.6.3", - "@polkadot/api": "^0.96.0-beta.14", + "@polkadot/api": "^0.96.0-beta.15", "@polkadot/extension-dapp": "^0.14.0-beta.1", "edgeware-node-types": "^1.0.10", "rxjs-compat": "^6.5.3" diff --git a/packages/react-components/src/Badge.tsx b/packages/react-components/src/Badge.tsx new file mode 100644 index 0000000000..f86b26f4d5 --- /dev/null +++ b/packages/react-components/src/Badge.tsx @@ -0,0 +1,104 @@ +// Copyright 2017-2019 @polkadot/app-staking authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import React, { useState } from 'react'; +import styled from 'styled-components'; + +import Tooltip from './Tooltip'; + +interface Props { + className?: string; + hover: React.ReactNode; + info: React.ReactNode; + isTooltip?: boolean; + type: 'online' | 'offline' | 'next'; +} + +let badgeId = 0; + +function Badge ({ className, hover, info, isTooltip, type }: Props): React.ReactElement | null { + const [key] = useState(`${Date.now()}-${badgeId++}`); + const [isOpen, setIsOpen] = useState(false); + + const _toggleOpen = (): void => setIsOpen(!isOpen); + + return ( +
+
+ {info} +
+
+ {hover} +
+ +
+ ); +} + +export default styled(Badge)` + border-radius: 16px; + box-shadow: 0 3px 3px rgba(0, 0, 0, 0.2); + color: #eee; + cursor: help; + display: flex; + font-size: 12px; + height: 22px; + justify-content: center; + margin-bottom: 0.25rem; + padding: 0 4px; + text-align: center; + width: 22px; + + &.next { + background: steelblue; + } + + &.offline { + background: red; + } + + &.online { + background: green; + } + + & > * { + line-height: 22px; + overflow: hidden; + transition: all ease 0.25; + } + + .badge { + font-weight: bold; + width: auto; + } + + .detail { + width: 0; + } + + &.expand { + width: 300px; + + .badge { + width: 0; + } + + .detail { + width: auto; + } + } +`; diff --git a/packages/react-components/src/OnlineStatus.tsx b/packages/react-components/src/OnlineStatus.tsx index 2e1e406d1d..d526681923 100644 --- a/packages/react-components/src/OnlineStatus.tsx +++ b/packages/react-components/src/OnlineStatus.tsx @@ -3,156 +3,73 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { DerivedStakingOnlineStatus } from '@polkadot/api-derive/types'; -import { AccountId } from '@polkadot/types/interfaces'; import { I18nProps } from './types'; import BN from 'bn.js'; import React, { useEffect, useState } from 'react'; -import styled from 'styled-components'; import { formatNumber } from '@polkadot/util'; +import Badge from './Badge'; import Icon from './Icon'; -import Tooltip from './Tooltip'; import translate from './translate'; -import { classes } from './util'; interface Props extends I18nProps { - accountId: AccountId | string; - tooltip?: boolean; + isTooltip?: boolean; value: DerivedStakingOnlineStatus; } -function OnlineStatus ({ accountId, className, value, tooltip = false, t }: Props): React.ReactElement | null { - const [isOpen, setIsOpen] = useState(false); - const [version, setVersion] = useState(null); - - const _toggleOpen = (): void => setIsOpen(!isOpen); +function OnlineStatus ({ className, value, isTooltip = false, t }: Props): React.ReactElement | null { + const [{ hover, info, type }, setType] = useState<{ hover: React.ReactNode; info: React.ReactNode; type: 'online' | 'offline' | null }>({ hover: '', info: '', type: null }); useEffect((): void => { const { online, offline } = value; + let hover: React.ReactNode = ''; + let info: React.ReactNode = ''; + let type: 'online' | 'offline' | null = null; if (offline) { - setVersion('offline'); - } else if (online && online.isOnline) { - setVersion('online'); - } else { - setVersion(null); - } - }, [value]); + const count = offline.reduce((total, { count }): BN => total.add(count), new BN(0)); + const blockNumbers = offline.map(({ blockNumber }): string => `#${formatNumber(blockNumber)}`); - const key = accountId.toString(); - const classNames: (string | false | undefined)[] = ['ui--OnlineStatus', isOpen && 'expand', tooltip && 'tooltip', className]; - let text: string; - let contents: React.ReactNode; - let offline; - let count; - let blockNumbers; - let blockNumber; - - switch (version) { - case 'offline': - offline = value.offline || []; - count = offline.reduce((total, { count }): BN => total.add(count), new BN(0)); - blockNumbers = offline.map(({ blockNumber }): string => `#${formatNumber(blockNumber)}`); - - classNames.push('offline'); - contents = count.toString(); - text = t('Reported offline {{count}} times, last at {{blockNumber}}', { + info = count.toString(); + hover = t('Reported offline {{count}} times, last at {{blockNumber}}', { replace: { count, blockNumber: blockNumbers[blockNumbers.length - 1] } }); - break; + type = 'offline'; + } else if (online && online.isOnline) { + const blockNumber = value.online ? value.online.blockNumber : null; - case 'online': - classNames.push('online'); - blockNumber = value.online ? value.online.blockNumber : null; - contents = ; - text = blockNumber + info = ; + hover = blockNumber ? t('Reported online at #{{blockNumber}}', { replace: { blockNumber: formatNumber(blockNumber) } }) : t('Reported online in the current session'); - break; + type = 'online'; + } - default: - return null; + setType({ hover, info, type }); + }, [value]); + + if (!type) { + return null; } return ( -
-
- {contents} -
-
- {text} -
- -
+ ); } -export default translate( - styled(OnlineStatus)` - border-radius: 16px; - box-shadow: 0 3px 3px rgba(0, 0, 0, 0.2); - color: #eee; - cursor: help; - display: flex; - font-size: 12px; - height: 22px; - justify-content: center; - padding: 0 4px; - text-align: center; - transition: all ease .2s; - width: 22px; - - &.offline { - background: red; - } - - &.online { - background: green; - } - - & > * { - line-height: 22px; - overflow: hidden; - transition: all ease 0.25; - } - - .badge { - font-weight: bold; - width: auto; - } - - .detail { - width: 0; - } - - &.expand { - width: 300px; - - .badge { - width: 0; - } - - .detail { - width: auto; - } - } - ` -); +export default translate(OnlineStatus); diff --git a/packages/react-components/src/index.tsx b/packages/react-components/src/index.tsx index d0540c39c6..ebf7090854 100644 --- a/packages/react-components/src/index.tsx +++ b/packages/react-components/src/index.tsx @@ -8,6 +8,7 @@ export { default as AddressInfo } from './AddressInfo'; export { default as AddressMini } from './AddressMini'; export { default as AddressRow } from './AddressRow'; export { default as Available } from './Available'; +export { default as Badge } from './Badge'; export { default as Balance } from './Balance'; export { default as Bonded } from './Bonded'; export { default as Bubble } from './Bubble'; diff --git a/yarn.lock b/yarn.lock index a8b6a3e968..f03ff65e6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2016,45 +2016,45 @@ once "^1.4.0" universal-user-agent "^4.0.0" -"@polkadot/api-contract@^0.96.0-beta.14": - version "0.96.0-beta.14" - resolved "https://registry.yarnpkg.com/@polkadot/api-contract/-/api-contract-0.96.0-beta.14.tgz#26587385589cd5a351cfc4a5694ddde8a9033a26" - integrity sha512-78c1YR2YTqAden9APlu40noELrSR/dOHhGRFmk7zkoyeAaHgPH65An2lQBcEU4fzBUhjorWjb1Pd7IBbW/GK7Q== +"@polkadot/api-contract@^0.96.0-beta.15": + version "0.96.0-beta.15" + resolved "https://registry.yarnpkg.com/@polkadot/api-contract/-/api-contract-0.96.0-beta.15.tgz#25c910de8535a52daf7a08dc8c19cb4c74985a21" + integrity sha512-QXQANhO2BrtORbQgIHEhEhM8JIMwo30Axhug5xKa9SQQ/FnHWt0dslU9qutGfqJngGFE0HSyn1WSrPoBXgZSzA== dependencies: "@babel/runtime" "^7.6.3" - "@polkadot/types" "^0.96.0-beta.14" + "@polkadot/types" "^0.96.0-beta.15" -"@polkadot/api-derive@^0.96.0-beta.14": - version "0.96.0-beta.14" - resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-0.96.0-beta.14.tgz#ae7faf61965f5934a683ecae23f46fb6243dc793" - integrity sha512-w92geU6JQDCoKJ/pPKIUitYNNcdgsDj2NLH5I1JBtMBhu6JGb5YZKDwyKH1Q4fmBk7gLD1ibyU/i1NiY945xEg== +"@polkadot/api-derive@^0.96.0-beta.15": + version "0.96.0-beta.15" + resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-0.96.0-beta.15.tgz#a0bb19445eccee7f480d87724e5b31d398c59bab" + integrity sha512-0Dg9QJUapk4GAJIHIYCw9hgpeZNBYiI797rtv1njWu6Pcpqv2TwrrHFiSZT6uC7Xz2r7k+prkjNhf42Q74T1Jg== dependencies: "@babel/runtime" "^7.6.3" - "@polkadot/api" "^0.96.0-beta.14" - "@polkadot/types" "^0.96.0-beta.14" + "@polkadot/api" "^0.96.0-beta.15" + "@polkadot/types" "^0.96.0-beta.15" -"@polkadot/api-metadata@^0.96.0-beta.14": - version "0.96.0-beta.14" - resolved "https://registry.yarnpkg.com/@polkadot/api-metadata/-/api-metadata-0.96.0-beta.14.tgz#5eb4054b943e8af8ee2d39379b7695ac8301a107" - integrity sha512-g1tqyan014T9qZdLSi6lNeOozQ0gfJFS6SPx3TDqTbFm4ITVoviq+53n/+7VDf4HrRmC28Yp5Aku3pvUfPjSWA== +"@polkadot/api-metadata@^0.96.0-beta.15": + version "0.96.0-beta.15" + resolved "https://registry.yarnpkg.com/@polkadot/api-metadata/-/api-metadata-0.96.0-beta.15.tgz#4d94e41e681f4bd68d4dab22bbbedbd89b23c29e" + integrity sha512-SFjoMRtQzxE35y39Rw0+ujvVif6gC015rKcjPsZbRTbbrleJsNNvwSm0OuqEUVRmnQShkafConM6AFv1Cbyigw== dependencies: "@babel/runtime" "^7.6.3" - "@polkadot/types" "^0.96.0-beta.14" + "@polkadot/types" "^0.96.0-beta.15" "@polkadot/util" "^1.6.1" "@polkadot/util-crypto" "^1.6.1" -"@polkadot/api@^0.96.0-beta.14": - version "0.96.0-beta.14" - resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-0.96.0-beta.14.tgz#e9979c874549a876b388dd476eccd1a4870f11db" - integrity sha512-WmQMwhwyF6ymhOwpyiymOAlLByb1rkew+OkCaZqF3N2sSy9E15XFJR5MjnrqN/Yz64GRqrCrFGKAgVl14zaxgA== +"@polkadot/api@^0.96.0-beta.15": + version "0.96.0-beta.15" + resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-0.96.0-beta.15.tgz#e12ac1ff2dd5136bbbeb8e7c9695c16ca965b31d" + integrity sha512-9D5je8yQxQl/tbNU5vdF4FLCFH5in0XDaxdmhOd2E8/HN/JyEhISL+t/S0x787VcoOcch6GI4P4Qpb0a3BXEDg== dependencies: "@babel/runtime" "^7.6.3" - "@polkadot/api-derive" "^0.96.0-beta.14" - "@polkadot/api-metadata" "^0.96.0-beta.14" + "@polkadot/api-derive" "^0.96.0-beta.15" + "@polkadot/api-metadata" "^0.96.0-beta.15" "@polkadot/keyring" "^1.6.1" - "@polkadot/rpc-core" "^0.96.0-beta.14" - "@polkadot/rpc-provider" "^0.96.0-beta.14" - "@polkadot/types" "^0.96.0-beta.14" + "@polkadot/rpc-core" "^0.96.0-beta.15" + "@polkadot/rpc-provider" "^0.96.0-beta.15" + "@polkadot/types" "^0.96.0-beta.15" "@polkadot/util-crypto" "^1.6.1" "@polkadot/dev-react@^0.32.0-beta.12": @@ -2156,10 +2156,10 @@ dependencies: "@babel/runtime" "^7.6.3" -"@polkadot/jsonrpc@^0.96.0-beta.14": - version "0.96.0-beta.14" - resolved "https://registry.yarnpkg.com/@polkadot/jsonrpc/-/jsonrpc-0.96.0-beta.14.tgz#c68db9f3cffec7dc0dc103c2f13ef91a35293dc1" - integrity sha512-pg9S+Pjl/IQAYanvz6hMEDUzujnqbcPzzhQIKIfY6dl1y1iQBWSqGqV/tKhWjK41VEqZYdmhG6R+kIL9yQUfLA== +"@polkadot/jsonrpc@^0.96.0-beta.15": + version "0.96.0-beta.15" + resolved "https://registry.yarnpkg.com/@polkadot/jsonrpc/-/jsonrpc-0.96.0-beta.15.tgz#d885b1b17386a8e87dbc5951e8be88ca775a962f" + integrity sha512-db08i+S45Gy8c8apM2DtXZVN4bV3pWOmnoKxFxsW7fFatQ0lAeAe2xMdyMSdoU7UiU7OzMO/BIdwLkJe4o3Jnw== dependencies: "@babel/runtime" "^7.6.3" @@ -2197,25 +2197,25 @@ qrcode-generator "^1.4.4" react-qr-reader "^2.2.1" -"@polkadot/rpc-core@^0.96.0-beta.14": - version "0.96.0-beta.14" - resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-0.96.0-beta.14.tgz#285dc0109f949df1f2a273b15818faf9dbf2cdec" - integrity sha512-K6EYCNMFdAtkAicMeN8/HMDIXWE9eELCzKX5LrdKVcJhnI8lAY2WL1fiWbkfYLmUyV1+30JYC2B6nFmnakO9Nw== +"@polkadot/rpc-core@^0.96.0-beta.15": + version "0.96.0-beta.15" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-0.96.0-beta.15.tgz#a1ca1fb0d37c68a0107313d9bfd67bf1c2e3ac01" + integrity sha512-aLRlfe1tOyMhJ5A2BCX82qHAJs4Nl/x8AIlvLs5Wme/Jl5eo+N3Y+R3p+uF99JIosrzJmdcdjBG8qGY3Cj4PvQ== dependencies: "@babel/runtime" "^7.6.3" - "@polkadot/jsonrpc" "^0.96.0-beta.14" - "@polkadot/rpc-provider" "^0.96.0-beta.14" - "@polkadot/types" "^0.96.0-beta.14" + "@polkadot/jsonrpc" "^0.96.0-beta.15" + "@polkadot/rpc-provider" "^0.96.0-beta.15" + "@polkadot/types" "^0.96.0-beta.15" "@polkadot/util" "^1.6.1" rxjs "^6.5.3" -"@polkadot/rpc-provider@^0.96.0-beta.14": - version "0.96.0-beta.14" - resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-0.96.0-beta.14.tgz#7565cf4fe2138bbe4e984fa0b88ed2925e497b7f" - integrity sha512-9t9xmj3Zy+0lOd4iZirkUsUB/5bDlHFTTAcuwgB2MtqLi9m63JJaXjcWWa1HaYmg9u+gycbwMOg7jFSlf6RjjQ== +"@polkadot/rpc-provider@^0.96.0-beta.15": + version "0.96.0-beta.15" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-0.96.0-beta.15.tgz#d540ed73dd2299eccd4fb53793fbd1addbe33890" + integrity sha512-A2QGImrRMRmZXZLgaNjqayIrerx8fi1+74KO4yx3wcMx7V7kEvZ/B7X00vEO4eg9ysRw/INcfZD67eWu0yjRbg== dependencies: "@babel/runtime" "^7.6.3" - "@polkadot/api-metadata" "^0.96.0-beta.14" + "@polkadot/api-metadata" "^0.96.0-beta.15" "@polkadot/util" "^1.6.1" "@polkadot/util-crypto" "^1.6.1" "@types/nock" "^11.1.0" @@ -2230,10 +2230,10 @@ dependencies: "@types/chrome" "^0.0.91" -"@polkadot/types@^0.96.0-beta.14": - version "0.96.0-beta.14" - resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-0.96.0-beta.14.tgz#88a3899d6f3a96eab4d7abc66cc903023ad0c88b" - integrity sha512-7icPtEkZXoukKqN2hTBEyZH9shwV7At2xVqLjEx8j4r6IP+gDL2UbvcQ2WevCsIeWfsnaN13pEMSbClEuWOuVQ== +"@polkadot/types@^0.96.0-beta.15": + version "0.96.0-beta.15" + resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-0.96.0-beta.15.tgz#8b9acea0acb10e708f5ac8f5fa47450fb6e6ffb5" + integrity sha512-ufWNAkz7fy4HlsHzJuBkLbIHMXjZaskaGwoBAT0HOBJ7wI3NSz78ScF69beVY5KwG+9Qc0dSYvYWFDOmDJTJCQ== dependencies: "@babel/runtime" "^7.6.3" "@polkadot/util" "^1.6.1"