// Copyright 2017-2021 @polkadot/react-components authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { TFunction } from 'i18next'; import type { DeriveDemocracyLock } from '@polkadot/api-derive/types'; import BN from 'bn.js'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { useApi, useCall } from '@polkadot/react-hooks'; import { BlockToTime, FormatBalance } from '@polkadot/react-query'; import { BN_ZERO, bnMax, formatBalance, formatNumber } from '@polkadot/util'; import Icon from './Icon'; import Tooltip from './Tooltip'; import { useTranslation } from './translate'; interface Props { className?: string; value?: DeriveDemocracyLock[]; } interface Entry { details: React.ReactNode; headers: React.ReactNode[]; isCountdown: boolean; isFinished: boolean; } interface State { maxBalance: BN; sorted: Entry[]; } let id = 0; // group by header & details // - all unlockable together // - all ongoing together // - unlocks are displayed individually function groupLocks (t: TFunction, bestNumber: BN, locks: DeriveDemocracyLock[] = []): State { return { maxBalance: bnMax(...locks.map(({ balance }) => balance)), sorted: locks .map((info): [DeriveDemocracyLock, BN] => [info, info.unlockAt.gt(bestNumber) ? info.unlockAt.sub(bestNumber) : BN_ZERO]) .sort((a, b) => a[0].referendumId.cmp(b[0].referendumId)) .sort((a, b) => a[1].cmp(b[1])) .sort((a, b) => a[0].isFinished === b[0].isFinished ? 0 : (a[0].isFinished ? -1 : 1)) .reduce((sorted: Entry[], [{ balance, isDelegated, isFinished, referendumId, vote }, blocks]): Entry[] => { const isCountdown = blocks.gt(BN_ZERO); const header =
#{referendumId.toString()} {formatBalance(balance, { forceUnit: '-' })} {vote.conviction.toString()}{isDelegated && '/d'}
; const prev = sorted.length ? sorted[sorted.length - 1] : null; if (!prev || (isCountdown || (isFinished !== prev.isFinished))) { sorted.push({ details: (
{isCountdown ? ( ('{{blocks}} blocks', { replace: { blocks: formatNumber(blocks) } })}, `} /> ) : isFinished ? t('lock expired') : t('ongoing referendum') }
), headers: [header], isCountdown, isFinished }); } else { prev.headers.push(header); } return sorted; }, []) }; } function DemocracyLocks ({ className = '', value }: Props): React.ReactElement | null { const { t } = useTranslation(); const { api } = useApi(); const bestNumber = useCall(api.derive.chain.bestNumber); const [trigger] = useState(`${Date.now()}-democracy-locks-${++id}`); const [{ maxBalance, sorted }, setState] = useState({ maxBalance: BN_ZERO, sorted: [] }); useEffect((): void => { bestNumber && setState((state): State => { const newState = groupLocks(t, bestNumber, value); // only update when the structure of new is different // - it has a new overall breakdown with sections // - one of the sections has a different number of headers return state.sorted.length !== newState.sorted.length || state.sorted.some((s, i) => s.headers.length !== newState.sorted[i].headers.length) ? newState : state; }); }, [bestNumber, t, value]); if (!sorted.length) { return null; } return (
(
{headers.map((header, index) => (
{header}
))}
{details}
))} trigger={trigger} />
); } export default React.memo(styled(DemocracyLocks)` white-space: nowrap; .ui--Icon { margin-left: 0; margin-right: 0.25rem; } .ui--FormatBalance { display: inline-block; } `);