CurrentList.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Copyright 2017-2020 @polkadot/app-staking authors & contributors
  2. // This software may be modified and distributed under the terms
  3. // of the Apache-2.0 license. See the LICENSE file for details.
  4. import { DeriveHeartbeats, DeriveStakingOverview } from '@polkadot/api-derive/types';
  5. import { AccountId, Nominations } from '@polkadot/types/interfaces';
  6. import { Authors } from '@polkadot/react-query/BlockAuthors';
  7. import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
  8. import { Table } from '@polkadot/react-components';
  9. import { useApi, useCall } from '@polkadot/react-hooks';
  10. import { BlockAuthorsContext } from '@polkadot/react-query';
  11. import { Option, StorageKey } from '@polkadot/types';
  12. import Filtering from '../Filtering';
  13. import { useTranslation } from '../translate';
  14. import Address from './Address';
  15. interface Props {
  16. favorites: string[];
  17. hasQueries: boolean;
  18. isIntentions?: boolean;
  19. next?: string[];
  20. setNominators?: (nominators: string[]) => void;
  21. stakingOverview?: DeriveStakingOverview;
  22. toggleFavorite: (address: string) => void;
  23. }
  24. type AccountExtend = [string, boolean, boolean];
  25. interface Filtered {
  26. elected?: AccountExtend[];
  27. validators?: AccountExtend[];
  28. waiting?: AccountExtend[];
  29. }
  30. const EmptyAuthorsContext: React.Context<Authors> = React.createContext<Authors>({ byAuthor: {}, eraPoints: {}, lastBlockAuthors: [], lastHeaders: [] });
  31. function filterAccounts (accounts: string[] = [], elected: string[], favorites: string[], without: string[]): AccountExtend[] {
  32. return accounts
  33. .filter((accountId): boolean => !without.includes(accountId as any))
  34. .map((accountId): AccountExtend => [
  35. accountId,
  36. elected.includes(accountId),
  37. favorites.includes(accountId)
  38. ])
  39. .sort(([,, isFavA]: AccountExtend, [,, isFavB]: AccountExtend): number =>
  40. isFavA === isFavB
  41. ? 0
  42. : (isFavA ? -1 : 1)
  43. );
  44. }
  45. function accountsToString (accounts: AccountId[]): string[] {
  46. return accounts.map((accountId): string => accountId.toString());
  47. }
  48. function getFiltered (stakingOverview: DeriveStakingOverview, favorites: string[], next?: string[]): Filtered {
  49. const allElected = accountsToString(stakingOverview.nextElected);
  50. const validatorIds = accountsToString(stakingOverview.validators);
  51. const validators = filterAccounts(validatorIds, allElected, favorites, []);
  52. const elected = filterAccounts(allElected, allElected, favorites, validatorIds);
  53. const waiting = filterAccounts(next, [], favorites, allElected);
  54. return {
  55. elected,
  56. validators,
  57. waiting
  58. };
  59. }
  60. function extractNominators (nominations: [StorageKey, Option<Nominations>][]): Record<string, [string, number][]> {
  61. return nominations.reduce((mapped: Record<string, [string, number][]>, [key, optNoms]) => {
  62. if (optNoms.isSome) {
  63. const nominatorId = key.args[0].toString();
  64. optNoms.unwrap().targets.forEach((_validatorId, index): void => {
  65. const validatorId = _validatorId.toString();
  66. const info: [string, number] = [nominatorId, index + 1];
  67. if (!mapped[validatorId]) {
  68. mapped[validatorId] = [info];
  69. } else {
  70. mapped[validatorId].push(info);
  71. }
  72. });
  73. }
  74. return mapped;
  75. }, {});
  76. }
  77. function CurrentList ({ favorites, hasQueries, isIntentions, next, stakingOverview, toggleFavorite }: Props): React.ReactElement<Props> | null {
  78. const { t } = useTranslation();
  79. const { api } = useApi();
  80. const { byAuthor, eraPoints } = useContext(isIntentions ? EmptyAuthorsContext : BlockAuthorsContext);
  81. const recentlyOnline = useCall<DeriveHeartbeats>(!isIntentions && api.derive.imOnline?.receivedHeartbeats, []);
  82. const nominators = useCall<[StorageKey, Option<Nominations>][]>(isIntentions && api.query.staking.nominators.entries as any, []);
  83. const [{ elected, validators, waiting }, setFiltered] = useState<Filtered>({});
  84. const [nameFilter, setNameFilter] = useState<string>('');
  85. const [nominatedBy, setNominatedBy] = useState<Record<string, [string, number][]> | null>();
  86. const [withIdentity, setWithIdentity] = useState(false);
  87. useEffect((): void => {
  88. stakingOverview && setFiltered(
  89. getFiltered(stakingOverview, favorites, next)
  90. );
  91. }, [favorites, next, stakingOverview]);
  92. useEffect((): void => {
  93. nominators && setNominatedBy(
  94. extractNominators(nominators)
  95. );
  96. }, [nominators]);
  97. const headerWaiting = useMemo(() => [
  98. [t('intentions'), 'start', 2],
  99. [t('nominators'), 'start', 2],
  100. [t('commission'), 'number', 1],
  101. []
  102. ], [t]);
  103. const headerActive = useMemo(() => [
  104. [t('validators'), 'start', 2],
  105. [t('other stake')],
  106. [t('own stake')],
  107. [t('total stake')],
  108. [t('commission')],
  109. [t('points')],
  110. [t('last #')],
  111. []
  112. ], [t]);
  113. const _renderRows = useCallback(
  114. (addresses?: AccountExtend[], isMain?: boolean): React.ReactNode[] =>
  115. (addresses || []).map(([address, isElected, isFavorite]): React.ReactNode => (
  116. <Address
  117. address={address}
  118. filterName={nameFilter}
  119. hasQueries={hasQueries}
  120. isElected={isElected}
  121. isFavorite={isFavorite}
  122. isMain={isMain}
  123. key={address}
  124. lastBlock={byAuthor[address]}
  125. nominatedBy={nominatedBy ? (nominatedBy[address] || []) : undefined}
  126. onlineCount={recentlyOnline?.[address]?.blockCount.toNumber()}
  127. onlineMessage={recentlyOnline?.[address]?.hasMessage}
  128. points={eraPoints[address]}
  129. toggleFavorite={toggleFavorite}
  130. withIdentity={withIdentity}
  131. />
  132. )),
  133. [byAuthor, eraPoints, hasQueries, nameFilter, nominatedBy, recentlyOnline, toggleFavorite, withIdentity]
  134. );
  135. return isIntentions
  136. ? (
  137. <Table
  138. empty={waiting && t<string>('No waiting validators found')}
  139. filter={
  140. <Filtering
  141. nameFilter={nameFilter}
  142. setNameFilter={setNameFilter}
  143. setWithIdentity={setWithIdentity}
  144. withIdentity={withIdentity}
  145. />
  146. }
  147. header={headerWaiting}
  148. >
  149. {_renderRows(elected, false).concat(_renderRows(waiting, false))}
  150. </Table>
  151. )
  152. : (
  153. <Table
  154. empty={validators && t<string>('No active validators found')}
  155. filter={
  156. <Filtering
  157. nameFilter={nameFilter}
  158. setNameFilter={setNameFilter}
  159. setWithIdentity={setWithIdentity}
  160. withIdentity={withIdentity}
  161. />
  162. }
  163. header={headerActive}
  164. >
  165. {_renderRows(validators, true)}
  166. </Table>
  167. );
  168. }
  169. export default React.memo(CurrentList);