From 1f51e600d19f35ec1cb493b249dc4729ac104788 Mon Sep 17 00:00:00 2001 From: kwingram25 Date: Wed, 28 Aug 2019 16:12:24 +0200 Subject: [PATCH] Display ReceivedHeartbeats status for staking accounts (#1476) * Display ReceivedHeartbeats status for staking accounts * Fix * linting error * complete merge * fix changed event sig * fix recentlyOnline check --- .../app-staking/src/Actions/Account/index.tsx | 36 ++-- packages/app-staking/src/Actions/Accounts.tsx | 8 +- packages/app-staking/src/Overview/Address.tsx | 43 +++-- .../app-staking/src/Overview/CurrentList.tsx | 9 +- packages/app-staking/src/Overview/index.tsx | 4 +- packages/app-staking/src/index.tsx | 67 ++++--- packages/app-staking/src/types.ts | 13 +- packages/app-staking/src/util.ts | 31 ++++ .../react-components/src/OnlineStatus.tsx | 175 ++++++++++++++++++ .../react-components/src/RecentlyOffline.tsx | 117 ------------ packages/react-components/src/index.tsx | 2 +- 11 files changed, 303 insertions(+), 202 deletions(-) create mode 100644 packages/app-staking/src/util.ts create mode 100644 packages/react-components/src/OnlineStatus.tsx delete mode 100644 packages/react-components/src/RecentlyOffline.tsx diff --git a/packages/app-staking/src/Actions/Account/index.tsx b/packages/app-staking/src/Actions/Account/index.tsx index 89b926e784..60f710cf30 100644 --- a/packages/app-staking/src/Actions/Account/index.tsx +++ b/packages/app-staking/src/Actions/Account/index.tsx @@ -3,17 +3,16 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { RecentlyOfflineMap } from '../../types'; -import { AccountId, Exposure, StakingLedger, ValidatorPrefs } from '@polkadot/types/interfaces'; +import { DerivedBalances, DerivedStaking, DerivedStakingOnlineStatus } from '@polkadot/api-derive/types'; import { ApiProps } from '@polkadot/react-api/types'; -import { DerivedBalances, DerivedStaking } from '@polkadot/api-derive/types'; import { I18nProps } from '@polkadot/react-components/types'; +import { AccountId, BlockNumber, Exposure, StakingLedger, ValidatorPrefs } from '@polkadot/types/interfaces'; import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types'; import { Popup } from 'semantic-ui-react'; import React from 'react'; import styled from 'styled-components'; -import { AddressCard, AddressInfo, AddressMini, AddressRow, Button, Menu, RecentlyOffline, TxButton } from '@polkadot/react-components'; +import { AddressCard, AddressInfo, AddressMini, AddressRow, Button, Menu, OnlineStatus, TxButton } from '@polkadot/react-components'; import { withCalls, withMulti } from '@polkadot/react-api'; import BondExtra from './BondExtra'; @@ -26,12 +25,14 @@ import Unbond from './Unbond'; import Validate from './Validate'; import { u8aToHex, u8aConcat } from '@polkadot/util'; +import { updateOnlineStatus } from '../../util'; + type Props = ApiProps & I18nProps & { accountId: string; allStashes?: string[]; balances_all?: DerivedBalances; className?: string; - recentlyOffline: RecentlyOfflineMap; + recentlyOnline: Record; staking_info?: DerivedStaking; stashOptions: KeyringSectionOption[]; }; @@ -51,6 +52,7 @@ interface State { isUnbondOpen: boolean; isValidateOpen: boolean; nominees?: string[]; + onlineStatus: DerivedStakingOnlineStatus; sessionIds: string[]; stakers?: Exposure; stakingLedger?: StakingLedger; @@ -79,16 +81,17 @@ class Account extends React.PureComponent { isStashValidating: false, isUnbondOpen: false, isValidateOpen: false, + onlineStatus: {}, sessionIds: [], stashId: null }; - public static getDerivedStateFromProps ({ allStashes, staking_info }: Props): Pick | null { + public static getDerivedStateFromProps ({ allStashes, recentlyOnline, staking_info }: Props): Pick | null { if (!staking_info) { return null; } - const { controllerId, nextSessionIds, nominators, rewardDestination, sessionIds, stakers, stakingLedger, stashId, validatorPrefs } = staking_info; + const { controllerId, nextSessionIds, nominators, online, offline, rewardDestination, sessionIds, stakers, stakingLedger, stashId, validatorPrefs } = staking_info; const isStashNominating = nominators && nominators.length !== 0; const _stashId = toIdString(stashId); const isStashValidating = !!allStashes && !!_stashId && allStashes.includes(_stashId); @@ -104,6 +107,7 @@ class Account extends React.PureComponent { isStashNominating, isStashValidating, nominees: nominators && nominators.map(toIdString), + onlineStatus: updateOnlineStatus(recentlyOnline)(sessionIds || null, { online, offline }), sessionIds: ( nextSessionIds.length ? nextSessionIds @@ -131,7 +135,7 @@ class Account extends React.PureComponent { return ( { {nominees.map((nomineeId, index): React.ReactNode => ( { ); } - private renderOffline (address: AccountId | string): React.ReactNode { - const { recentlyOffline } = this.props; + private renderOnlineStatus (): React.ReactNode { + const { onlineStatus, controllerId } = this.state; + + if (!controllerId || !onlineStatus) { + return null; + } return ( - ); @@ -262,7 +269,6 @@ class Account extends React.PureComponent { return (
{ }; public render (): React.ReactNode { - const { allStashes, className, recentlyOffline, t } = this.props; + const { allStashes, className, recentlyOnline, t } = this.props; const { isNewStakeOpen } = this.state; const stashOptions = this.getStashOptions(); const myStashes = this.getMyStashes(); @@ -64,7 +64,7 @@ class Accounts extends React.PureComponent { allStashes={allStashes} accountId={address} key={index} - recentlyOffline={recentlyOffline} + recentlyOnline={recentlyOnline} stashOptions={stashOptions} /> ))} @@ -112,14 +112,14 @@ class Accounts extends React.PureComponent { } private toggleNewStake = (): void => { - this.setState(({ isNewStakeOpen }): State => ({ + this.setState(({ isNewStakeOpen }): Pick => ({ isNewStakeOpen: !isNewStakeOpen })); } } export default withMulti( - styled(Accounts)` + styled(Accounts as React.ComponentClass)` .ui--CardGrid-buttons { text-align: right; } diff --git a/packages/app-staking/src/Overview/Address.tsx b/packages/app-staking/src/Overview/Address.tsx index 30e2e871be..74fa7f25be 100644 --- a/packages/app-staking/src/Overview/Address.tsx +++ b/packages/app-staking/src/Overview/Address.tsx @@ -3,19 +3,20 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { AccountId, Balance, Exposure } from '@polkadot/types/interfaces'; -import { DerivedBalancesMap, DerivedStaking } from '@polkadot/api-derive/types'; +import { AccountId, Balance, BlockNumber, Exposure } from '@polkadot/types/interfaces'; +import { DerivedBalancesMap, DerivedStaking, DerivedStakingOnlineStatus } from '@polkadot/api-derive/types'; import { ApiProps } from '@polkadot/react-api/types'; import { I18nProps } from '@polkadot/react-components/types'; -import { ValidatorFilter, RecentlyOfflineMap } from '../types'; +import { ValidatorFilter } from '../types'; import React from 'react'; import styled from 'styled-components'; import { withCalls, withMulti } from '@polkadot/react-api/with'; -import { AddressCard, AddressMini, RecentlyOffline } from '@polkadot/react-components'; +import { AddressCard, AddressMini, OnlineStatus } from '@polkadot/react-components'; import { classes } from '@polkadot/react-components/util'; import keyring from '@polkadot/ui-keyring'; import { formatBalance } from '@polkadot/util'; +import { updateOnlineStatus } from '../util'; import translate from '../translate'; @@ -24,15 +25,16 @@ interface Props extends ApiProps, I18nProps { balances: DerivedBalancesMap; className?: string; defaultName: string; + filter: ValidatorFilter; lastAuthor: string; lastBlock: string; - recentlyOffline: RecentlyOfflineMap; - filter: ValidatorFilter; + recentlyOnline?: Record; staking_info?: DerivedStaking; } interface State { controllerId: string | null; + onlineStatus: DerivedStakingOnlineStatus; stashActive: string | null; stashTotal: string | null; sessionId: string | null; @@ -49,6 +51,7 @@ class Address extends React.PureComponent { this.state = { controllerId: null, + onlineStatus: {}, sessionId: null, stashActive: null, stashId: null, @@ -57,15 +60,16 @@ class Address extends React.PureComponent { }; } - public static getDerivedStateFromProps ({ staking_info }: Props, prevState: State): Pick | null { + public static getDerivedStateFromProps ({ recentlyOnline = {}, staking_info }: Props, prevState: State): Pick | null { if (!staking_info) { return null; } - const { controllerId, nextSessionId, stakers, stashId, stakingLedger } = staking_info; + const { controllerId, nextSessionId, online, offline, stakers, stakingLedger, stashId } = staking_info; return { controllerId: controllerId && controllerId.toString(), + onlineStatus: updateOnlineStatus(recentlyOnline)(staking_info.sessionIds || null, { offline, online }), sessionId: nextSessionId && nextSessionId.toString(), stashActive: stakingLedger ? formatBalance(stakingLedger.active) @@ -98,7 +102,7 @@ class Address extends React.PureComponent { buttons={this.renderKeys()} className={className} defaultName={defaultName} - iconInfo={this.renderOffline()} + iconInfo={this.renderOnlineStatus()} key={stashId || controllerId || undefined} value={stashId || address} withBalance={{ bonded }} @@ -165,10 +169,9 @@ class Address extends React.PureComponent { } private hasWarnings (): boolean { - const { recentlyOffline } = this.props; - const { stashId } = this.state; + const { stashId, onlineStatus } = this.state; - if (!stashId || !recentlyOffline[stashId]) { + if (!stashId || !onlineStatus.offline || !onlineStatus.offline.length) { return false; } @@ -204,20 +207,16 @@ class Address extends React.PureComponent { ); } - private renderOffline (): React.ReactNode { - const { recentlyOffline } = this.props; - const { stashId } = this.state; - - if (!stashId || !recentlyOffline[stashId]) { + private renderOnlineStatus (): React.ReactNode { + const { controllerId, onlineStatus } = this.state; + if (!controllerId || !onlineStatus) { return null; } - const offline = recentlyOffline[stashId]; - return ( - ); diff --git a/packages/app-staking/src/Overview/CurrentList.tsx b/packages/app-staking/src/Overview/CurrentList.tsx index 1c6a20fe13..297bc8584b 100644 --- a/packages/app-staking/src/Overview/CurrentList.tsx +++ b/packages/app-staking/src/Overview/CurrentList.tsx @@ -2,9 +2,10 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { BlockNumber } from '@polkadot/types/interfaces'; import { DerivedBalancesMap } from '@polkadot/api-derive/types'; import { I18nProps } from '@polkadot/react-components/types'; -import { ValidatorFilter, RecentlyOfflineMap } from '../types'; +import { ValidatorFilter } from '../types'; import React from 'react'; import { Columar, Column, Dropdown, FilterOverlay } from '@polkadot/react-components'; @@ -18,7 +19,7 @@ interface Props extends I18nProps { lastAuthor?: string; lastBlock: string; next: string[]; - recentlyOffline: RecentlyOfflineMap; + recentlyOnline: Record; } interface State { @@ -79,7 +80,7 @@ class CurrentList extends React.PureComponent { } private renderColumn (addresses: string[], defaultName: string): React.ReactNode { - const { balances, lastAuthor, lastBlock, recentlyOffline } = this.props; + const { balances, lastAuthor, lastBlock, recentlyOnline } = this.props; const { filter } = this.state; return addresses.map((address): React.ReactNode => ( @@ -91,7 +92,7 @@ class CurrentList extends React.PureComponent { filter={filter} lastAuthor={lastAuthor} lastBlock={lastBlock} - recentlyOffline={recentlyOffline} + recentlyOnline={recentlyOnline} /> )); } diff --git a/packages/app-staking/src/Overview/index.tsx b/packages/app-staking/src/Overview/index.tsx index 5d8aadc7e0..dc7bcb9ce9 100644 --- a/packages/app-staking/src/Overview/index.tsx +++ b/packages/app-staking/src/Overview/index.tsx @@ -22,7 +22,7 @@ interface Props extends ApiProps, BareProps, ComponentProps { class Overview extends React.PureComponent { public render (): React.ReactNode { - const { chain_subscribeNewHeads, allControllers, allStashes, recentlyOffline, currentValidatorsControllersV1OrStashesV2, isSubstrateV2 } = this.props; + const { chain_subscribeNewHeads, allControllers, allStashes, currentValidatorsControllersV1OrStashesV2, isSubstrateV2, recentlyOnline } = this.props; let nextSorted: string[]; if (isSubstrateV2) { @@ -59,7 +59,7 @@ class Overview extends React.PureComponent { lastBlock={lastBlock} lastAuthor={lastAuthor} next={nextSorted} - recentlyOffline={recentlyOffline} + recentlyOnline={recentlyOnline} />
); diff --git a/packages/app-staking/src/index.tsx b/packages/app-staking/src/index.tsx index 1958ce856b..3e1e62e8be 100644 --- a/packages/app-staking/src/index.tsx +++ b/packages/app-staking/src/index.tsx @@ -3,15 +3,16 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { AccountId } from '@polkadot/types/interfaces'; +import { AccountId, AuthorityId, BlockNumber, EventRecord } from '@polkadot/types/interfaces'; import { AppProps, I18nProps } from '@polkadot/react-components/types'; import { ApiProps } from '@polkadot/react-api/types'; import { SubjectInfo } from '@polkadot/ui-keyring/observable/types'; -import { ComponentProps, RecentlyOffline, RecentlyOfflineMap } from './types'; +import { ComponentProps } from './types'; +import BN from 'bn.js'; import React from 'react'; import { Route, Switch } from 'react-router'; -import { Option } from '@polkadot/types'; +import { createType, Option } from '@polkadot/types'; import { HelpOverlay } from '@polkadot/react-components'; import Tabs, { TabItem } from '@polkadot/react-components/Tabs'; import { withCalls, withMulti, withObservable } from '@polkadot/react-api'; @@ -27,15 +28,16 @@ import translate from './translate'; type Props = AppProps & ApiProps & I18nProps & { allAccounts?: SubjectInfo; allStashesAndControllers?: [AccountId[], Option[]]; + chain_bestNumber?: BlockNumber; currentValidatorsControllersV1OrStashesV2?: AccountId[]; - staking_recentlyOffline?: RecentlyOffline; + recentlyOnline?: AuthorityId[]; }; interface State { allControllers: string[]; allStashes: string[]; currentValidatorsControllersV1OrStashesV2: string[]; - recentlyOffline: RecentlyOfflineMap; + recentlyOnline: Record; tabs: TabItem[]; } @@ -51,7 +53,7 @@ class App extends React.PureComponent { allControllers: [], allStashes: [], currentValidatorsControllersV1OrStashesV2: [], - recentlyOffline: {}, + recentlyOnline: {}, tabs: [ { isRoot: true, @@ -66,30 +68,29 @@ class App extends React.PureComponent { }; } - public static getDerivedStateFromProps ({ allStashesAndControllers = [[], []], currentValidatorsControllersV1OrStashesV2 = [], staking_recentlyOffline = [] }: Props): Pick { + public static getDerivedStateFromProps (props: Props, state: State): Pick { + const { allStashesAndControllers = [[], []], chain_bestNumber, currentValidatorsControllersV1OrStashesV2 = [] } = props; + + const recentlyOnline: Record = { + ...(state.recentlyOnline || {}), + ...(props.recentlyOnline || []).reduce( + (result: Record, authorityId): Record => ({ + ...result, + [authorityId.toString()]: chain_bestNumber || createType('BlockNumber', new BN(0)) + }), + {} + ) + }; + return { allControllers: allStashesAndControllers[1].filter((optId): boolean => optId.isSome).map((accountId): string => accountId.unwrap().toString() ), - allStashes: allStashesAndControllers[0].map((accountId): string => accountId.toString()), + allStashes: allStashesAndControllers[0].filter((): boolean => true).map((accountId): string => accountId.toString()), currentValidatorsControllersV1OrStashesV2: currentValidatorsControllersV1OrStashesV2.map((authorityId): string => authorityId.toString() ), - recentlyOffline: staking_recentlyOffline.reduce( - (result, [accountId, blockNumber, count]): RecentlyOfflineMap => { - const account = accountId.toString(); - - if (!result[account]) { - result[account] = []; - } - - result[account].push({ - blockNumber, - count - }); - - return result; - }, {} as unknown as RecentlyOfflineMap) + recentlyOnline }; } @@ -120,7 +121,7 @@ class App extends React.PureComponent { private renderComponent (Component: React.ComponentType): () => React.ReactNode { return (): React.ReactNode => { - const { allControllers, allStashes, currentValidatorsControllersV1OrStashesV2, recentlyOffline } = this.state; + const { allControllers, allStashes, currentValidatorsControllersV1OrStashesV2, recentlyOnline } = this.state; const { allAccounts } = this.props; if (!allAccounts) { @@ -133,7 +134,7 @@ class App extends React.PureComponent { allControllers={allControllers} allStashes={allStashes} currentValidatorsControllersV1OrStashesV2={currentValidatorsControllersV1OrStashesV2} - recentlyOffline={recentlyOffline} + recentlyOnline={recentlyOnline} /> ); }; @@ -144,9 +145,23 @@ export default withMulti( App, translate, withCalls( + 'derive.chain.bestNumber', ['derive.staking.controllers', { propName: 'allStashesAndControllers' }], ['query.session.validators', { propName: 'currentValidatorsControllersV1OrStashesV2' }], - 'query.staking.recentlyOffline' + [ + 'query.system.events', + { + propName: 'recentlyOnline', + transform: (value?: EventRecord[]): AuthorityId[] => + (value || []) + .filter(({ event: { method, section } }): boolean => + section === 'imOnline' && method === 'HeartbeatReceived' + ) + .map( + ({ event: { data: [authorityId] } }): AuthorityId => authorityId as AuthorityId + ) + } + ] ), withObservable(accountObservable.subject, { propName: 'allAccounts' }) ); diff --git a/packages/app-staking/src/types.ts b/packages/app-staking/src/types.ts index 7d25d60fb1..43d373cf5d 100644 --- a/packages/app-staking/src/types.ts +++ b/packages/app-staking/src/types.ts @@ -2,7 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { AccountId, BlockNumber } from '@polkadot/types/interfaces'; +import { BlockNumber } from '@polkadot/types/interfaces'; import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types'; import { SubjectInfo } from '@polkadot/ui-keyring/observable/types'; @@ -15,7 +15,7 @@ export interface ComponentProps { allControllers: string[]; allStashes: string[]; currentValidatorsControllersV1OrStashesV2: string[]; - recentlyOffline: RecentlyOfflineMap; + recentlyOnline: Record; } export interface CalculateBalanceProps { @@ -24,15 +24,6 @@ export interface CalculateBalanceProps { system_accountNonce?: BN; } -export type RecentlyOffline = [AccountId, BlockNumber, BN][]; - -export type RecentlyOfflineMap = Record; - -export interface OfflineStatus { - blockNumber: BlockNumber; - count: BN; -} - export type AccountFilter = 'all' | 'controller' | 'session' | 'stash' | 'unbonded'; export type ValidatorFilter = 'all' | 'hasNominators' | 'noNominators' | 'hasWarnings' | 'noWarnings' | 'iNominated'; diff --git a/packages/app-staking/src/util.ts b/packages/app-staking/src/util.ts new file mode 100644 index 0000000000..d1071635c5 --- /dev/null +++ b/packages/app-staking/src/util.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/camelcase */ +// 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 { AccountId, BlockNumber } from '@polkadot/types/interfaces'; +import { DerivedStakingOnlineStatus } from '@polkadot/api-derive/types'; + +export function updateOnlineStatus (recentlyOnline?: Record): (sessionIds: AccountId[] | null, onlineStatus: DerivedStakingOnlineStatus) => DerivedStakingOnlineStatus { + return (sessionIds: AccountId[] | null, onlineStatus: DerivedStakingOnlineStatus): DerivedStakingOnlineStatus => { + if (!recentlyOnline || !sessionIds) { + return onlineStatus; + } + + const sessionId: AccountId | undefined = sessionIds.find((accountId): boolean => Object.keys(recentlyOnline).includes(accountId.toString())); + + return { + ...onlineStatus, + ...( + sessionId + ? { + online: { + blockNumber: recentlyOnline[sessionId.toString()], + isOnline: true + } + } + : {} + ) + }; + }; +} diff --git a/packages/react-components/src/OnlineStatus.tsx b/packages/react-components/src/OnlineStatus.tsx new file mode 100644 index 0000000000..f8181aebb4 --- /dev/null +++ b/packages/react-components/src/OnlineStatus.tsx @@ -0,0 +1,175 @@ +// 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 { DerivedStakingOnlineStatus } from '@polkadot/api-derive/types'; +import { AccountId } from '@polkadot/types/interfaces'; +import { I18nProps } from './types'; + +import BN from 'bn.js'; +import React from 'react'; +import styled from 'styled-components'; +import { formatNumber } from '@polkadot/util'; + +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; + value: DerivedStakingOnlineStatus; +} + +interface State { + isOpen: boolean; + version: 'online' | 'offline' | null; +} + +class OnlineStatus extends React.PureComponent { + public static getDerivedStateFromProps ({ value = {} }: Props): Pick { + const { online, offline } = value; + + if (offline) { + return { version: 'offline' }; + } + if (online && online.isOnline) { + return { version: 'online' }; + } + return { version: null }; + } + + public state: State = { + isOpen: false, + version: null + }; + + public render (): React.ReactNode { + const { accountId, className, value, tooltip = false, t } = this.props; + const { isOpen, version } = this.state; + + 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}}', { + replace: { + count, + blockNumber: blockNumbers[blockNumbers.length - 1] + } + }); + break; + + case 'online': + blockNumber = value.online ? value.online.blockNumber : null; + + classNames.push('online'); + contents = ( + + ); + text = blockNumber + ? t('Reported online at #{{blockNumber}}', { + replace: { + blockNumber: formatNumber(blockNumber) + } + }) + : t('Reported online in the current session'); + break; + + default: + return null; + } + + return ( +
+
+ {contents} +
+
+ {text} +
+ +
+ ); + } + + private toggleOpen = (): void => { + this.setState(({ isOpen }): Pick => ({ + isOpen: !isOpen + })); + } +} + +export default translate(styled(OnlineStatus as React.ComponentClass)` + 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; + } + } +`); diff --git a/packages/react-components/src/RecentlyOffline.tsx b/packages/react-components/src/RecentlyOffline.tsx deleted file mode 100644 index fda3ed3ee2..0000000000 --- a/packages/react-components/src/RecentlyOffline.tsx +++ /dev/null @@ -1,117 +0,0 @@ -// 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 { AccountId } from '@polkadot/types/interfaces'; -import { I18nProps } from '@polkadot/react-components/types'; -import { OfflineStatus } from '@polkadot/app-staking/types'; - -import BN from 'bn.js'; -import React from 'react'; -import styled from 'styled-components'; -import { formatNumber } from '@polkadot/util'; - -import Tooltip from './Tooltip'; - -import translate from './translate'; -import { classes } from './util'; - -interface Props extends I18nProps { - accountId: AccountId | string; - offline: OfflineStatus[]; - tooltip?: boolean; -} - -interface State { - isOpen: boolean; -} - -class RecentlyOffline extends React.PureComponent { - public state: State = { - isOpen: false - }; - - public render (): React.ReactNode { - const { accountId, className, offline, tooltip = false, t } = this.props; - const { isOpen } = this.state; - - if (!offline) { - return null; - } - - const count = offline.reduce((total, { count }): BN => total.add(count), new BN(0)); - const blockNumbers = offline.map(({ blockNumber }): string => `#${formatNumber(blockNumber)}`); - const text = t('Reported offline {{count}} times, last at {{blockNumber}}', { - replace: { - count, - blockNumber: blockNumbers[blockNumbers.length - 1] - } - }); - - return ( -
-
{count.toString()}
-
{text}
- -
- ); - } - - private toggleOpen = (): void => { - this.setState(({ isOpen }): State => ({ - isOpen: !isOpen - })); - } -} - -export default translate(styled(RecentlyOffline)` - background: red; - 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; - text-align: center; - transition: all ease .2s; - width: 22px; - - & > * { - 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/index.tsx b/packages/react-components/src/index.tsx index 3b634d18d4..6b79e70632 100644 --- a/packages/react-components/src/index.tsx +++ b/packages/react-components/src/index.tsx @@ -59,7 +59,7 @@ export { default as Output } from './Output'; export { default as Password } from './Password'; export { default as Progress } from './Progress'; export { default as ProposedAction } from './ProposedAction'; -export { default as RecentlyOffline } from './RecentlyOffline'; +export { default as OnlineStatus } from './OnlineStatus'; export { default as Row } from './Row'; export { default as Static } from './Static'; export { default as Status } from './Status';