ghost-portal/packages/page-staking/src/Overview/CurrentList.tsx
Jaco Greeff d30b88b7f4
Adjust staking targets/overview sorting displays (#2999)
* Adjust staking targets/overview sorting displays

* Removce whack eslint bug comment
2020-06-20 14:02:42 +02:00

204 lines
6.9 KiB
TypeScript

// Copyright 2017-2020 @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 { DeriveHeartbeats, DeriveStakingOverview } from '@polkadot/api-derive/types';
import { AccountId, Nominations } from '@polkadot/types/interfaces';
import { Authors } from '@polkadot/react-query/BlockAuthors';
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { Input, Table, Toggle } from '@polkadot/react-components';
import { useApi, useCall, useFavorites } from '@polkadot/react-hooks';
import { BlockAuthorsContext } from '@polkadot/react-query';
import { Option, StorageKey } from '@polkadot/types';
import { STORE_FAVS_BASE } from '../constants';
import { useTranslation } from '../translate';
import Address from './Address';
interface Props {
hasQueries: boolean;
isIntentions?: boolean;
next?: string[];
setNominators?: (nominators: string[]) => void;
stakingOverview?: DeriveStakingOverview;
}
type AccountExtend = [string, boolean, boolean];
interface Filtered {
elected?: AccountExtend[];
validators?: AccountExtend[];
waiting?: AccountExtend[];
}
const EmptyAuthorsContext: React.Context<Authors> = React.createContext<Authors>({ byAuthor: {}, eraPoints: {}, lastBlockAuthors: [], lastHeaders: [] });
function filterAccounts (accounts: string[] = [], elected: string[], favorites: string[], without: string[]): AccountExtend[] {
return accounts
.filter((accountId): boolean => !without.includes(accountId as any))
.map((accountId): AccountExtend => [
accountId,
elected.includes(accountId),
favorites.includes(accountId)
])
.sort(([,, isFavA]: AccountExtend, [,, isFavB]: AccountExtend): number =>
isFavA === isFavB
? 0
: (isFavA ? -1 : 1)
);
}
function accountsToString (accounts: AccountId[]): string[] {
return accounts.map((accountId): string => accountId.toString());
}
function getFiltered (stakingOverview: DeriveStakingOverview, favorites: string[], next?: string[]): Filtered {
const allElected = accountsToString(stakingOverview.nextElected);
const validatorIds = accountsToString(stakingOverview.validators);
const validators = filterAccounts(validatorIds, allElected, favorites, []);
const elected = filterAccounts(allElected, allElected, favorites, validatorIds);
const waiting = filterAccounts(next, [], favorites, allElected);
return {
elected,
validators,
waiting
};
}
function extractNominators (nominations: [StorageKey, Option<Nominations>][]): Record<string, [string, number][]> {
return nominations.reduce((mapped: Record<string, [string, number][]>, [key, optNoms]) => {
if (optNoms.isSome) {
const nominatorId = key.args[0].toString();
optNoms.unwrap().targets.forEach((_validatorId, index): void => {
const validatorId = _validatorId.toString();
const info: [string, number] = [nominatorId, index + 1];
if (!mapped[validatorId]) {
mapped[validatorId] = [info];
} else {
mapped[validatorId].push(info);
}
});
}
return mapped;
}, {});
}
function CurrentList ({ hasQueries, isIntentions, next, stakingOverview }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const { byAuthor, eraPoints, lastBlockAuthors } = useContext(isIntentions ? EmptyAuthorsContext : BlockAuthorsContext);
const recentlyOnline = useCall<DeriveHeartbeats>(!isIntentions && api.derive.imOnline?.receivedHeartbeats, []);
const nominators = useCall<[StorageKey, Option<Nominations>][]>(isIntentions && api.query.staking.nominators.entries as any, []);
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE);
const [{ elected, validators, waiting }, setFiltered] = useState<Filtered>({});
const [nameFilter, setNameFilter] = useState<string>('');
const [nominatedBy, setNominatedBy] = useState<Record<string, [string, number][]> | null>();
const [withoutName, setWithoutName] = useState(true);
useEffect((): void => {
stakingOverview && setFiltered(
getFiltered(stakingOverview, favorites, next)
);
}, [favorites, next, stakingOverview]);
useEffect((): void => {
nominators && setNominatedBy(
extractNominators(nominators)
);
}, [nominators]);
const headerActive = useMemo(() => [
[t('intentions'), 'start', 3],
[t('nominators'), 'start', 2],
[t('commission'), 'number', 1],
[undefined, undefined, 3]
], [t]);
const headerWaiting = useMemo(() => [
[t('validators'), 'start', 3],
[t('other stake')],
[t('own stake')],
[t('commission')],
[t('points')],
[t('last #')],
[]
], [t]);
const filter = useMemo(() => (
<div>
<Input
autoFocus
isFull
label={t<string>('filter by name, address or index')}
onChange={setNameFilter}
value={nameFilter}
/>
<div className='staking--optionsBar'>
{api.query.identity && (
<Toggle
className='staking--buttonToggle'
label={
withoutName
? t<string>('with/without identity')
: t<string>('only with an identity')
}
onChange={setWithoutName}
value={withoutName}
/>
)}
</div>
</div>
), [api, nameFilter, t, withoutName]);
const _renderRows = useCallback(
(addresses?: AccountExtend[], isMain?: boolean): React.ReactNode[] =>
(addresses || []).map(([address, isElected, isFavorite]): React.ReactNode => (
<Address
address={address}
filterName={nameFilter}
hasQueries={hasQueries}
isAuthor={lastBlockAuthors.includes(address)}
isElected={isElected}
isFavorite={isFavorite}
isMain={isMain}
key={address}
lastBlock={byAuthor[address]}
nominatedBy={nominatedBy ? (nominatedBy[address] || []) : undefined}
onlineCount={recentlyOnline?.[address]?.blockCount.toNumber()}
onlineMessage={recentlyOnline?.[address]?.hasMessage}
points={eraPoints[address]}
toggleFavorite={toggleFavorite}
withoutName={withoutName}
/>
)),
[byAuthor, eraPoints, hasQueries, lastBlockAuthors, nameFilter, nominatedBy, recentlyOnline, toggleFavorite, withoutName]
);
return isIntentions
? (
<Table
empty={waiting && t<string>('No waiting validators found')}
filter={filter}
header={headerActive}
>
{_renderRows(elected, false).concat(..._renderRows(waiting, false))}
</Table>
)
: (
<Table
empty={validators && t<string>('No active validators found')}
filter={filter}
header={headerWaiting}
>
{_renderRows(validators, true)}
</Table>
);
}
export default React.memo(CurrentList);