Add staking favorites (top of list) (#1866)
This commit is contained in:
parent
6ddfaf0ef3
commit
2a52609f23
@ -24,8 +24,10 @@ interface Props extends I18nProps {
|
||||
defaultName: string;
|
||||
filter: ValidatorFilter;
|
||||
isElected: boolean;
|
||||
isFavorite: boolean;
|
||||
lastAuthors?: string[];
|
||||
myAccounts: string[];
|
||||
onFavorite: (accountId: string) => void;
|
||||
points?: Points;
|
||||
recentlyOnline?: DerivedHeartbeats;
|
||||
stakingInfo?: DerivedStaking;
|
||||
@ -44,7 +46,7 @@ interface StakingState {
|
||||
|
||||
const WITH_VALIDATOR_PREFS = { validatorPayment: true };
|
||||
|
||||
function Address ({ address, authorsMap, className, defaultName, filter, isElected, lastAuthors, myAccounts, points, recentlyOnline, stakingInfo, t, withNominations = true }: Props): React.ReactElement<Props> | null {
|
||||
function Address ({ address, authorsMap, className, defaultName, filter, isElected, isFavorite, lastAuthors, myAccounts, onFavorite, points, recentlyOnline, stakingInfo, t, withNominations = true }: Props): React.ReactElement<Props> | null {
|
||||
const { isSubstrateV2 } = useContext(ApiContext);
|
||||
const [extraInfo, setExtraInfo] = useState<[React.ReactNode, React.ReactNode][] | undefined>();
|
||||
const [hasActivity, setHasActivity] = useState(true);
|
||||
@ -120,13 +122,23 @@ function Address ({ address, authorsMap, className, defaultName, filter, isElect
|
||||
|
||||
const lastBlockNumber = authorsMap[stashId];
|
||||
const isAuthor = lastAuthors && lastAuthors.includes(stashId);
|
||||
const _onFavorite = (): void => onFavorite(stashId);
|
||||
|
||||
return (
|
||||
<AddressCard
|
||||
buttons={
|
||||
<div className='staking--Address-info'>
|
||||
{lastBlockNumber && (
|
||||
<div className={`blockNumberV${isSubstrateV2 ? '2' : '1'} ${isAuthor && 'isCurrent'}`}>#{lastBlockNumber}</div>
|
||||
{isSubstrateV2 && (
|
||||
<div className='extras'>
|
||||
{lastBlockNumber && (
|
||||
<div className={`blockNumberV${isSubstrateV2 ? '2' : '1'} ${isAuthor && 'isCurrent'}`}>#{lastBlockNumber}</div>
|
||||
)}
|
||||
<Icon
|
||||
className={`favorite ${isFavorite && 'isSelected'}`}
|
||||
name={isFavorite ? 'star' : 'star outline'}
|
||||
onClick={_onFavorite}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{controllerId && (
|
||||
<div>
|
||||
@ -200,6 +212,22 @@ function Address ({ address, authorsMap, className, defaultName, filter, isElect
|
||||
|
||||
export default withMulti(
|
||||
styled(Address)`
|
||||
.extras {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
.favorite {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: -0.25rem;
|
||||
|
||||
&.isSelected {
|
||||
color: goldenrod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.blockNumberV1,
|
||||
.blockNumberV2 {
|
||||
border-radius: 0.25rem;
|
||||
@ -220,7 +248,6 @@ export default withMulti(
|
||||
|
||||
.blockNumberV2 {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.25rem 0;
|
||||
|
||||
&.isCurrent {
|
||||
|
||||
@ -2,14 +2,15 @@
|
||||
// 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 { DerivedHeartbeats, DerivedStakingOverview } from '@polkadot/api-derive/types';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { AccountId } from '@polkadot/types/interfaces';
|
||||
import { ValidatorFilter } from '../types';
|
||||
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { ApiContext } from '@polkadot/react-api';
|
||||
import { Columar, Column, Dropdown, FilterOverlay } from '@polkadot/react-components';
|
||||
import store from 'store';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
|
||||
import translate from '../translate';
|
||||
@ -23,17 +24,21 @@ interface Props extends I18nProps {
|
||||
stakingOverview?: DerivedStakingOverview;
|
||||
}
|
||||
|
||||
function renderColumn (myAccounts: string[], addresses: AccountId[] | string[], defaultName: string, withOnline: boolean, filter: string, { authorsMap, lastAuthors, recentlyOnline, stakingOverview }: Props, pointIndexes?: number[]): React.ReactNode {
|
||||
return (addresses as AccountId[]).map((address, index): React.ReactNode => (
|
||||
const STORE_FAVS = 'staking:favorites';
|
||||
|
||||
function renderColumn (myAccounts: string[], favorites: string[], addresses: string[], defaultName: string, withOnline: boolean, filter: string, onFavorite: (accountId: string) => void, { authorsMap, lastAuthors, recentlyOnline, stakingOverview }: Props, pointIndexes?: number[]): React.ReactNode {
|
||||
return addresses.map((address, index): React.ReactNode => (
|
||||
<Address
|
||||
address={address}
|
||||
authorsMap={authorsMap}
|
||||
defaultName={defaultName}
|
||||
filter={filter}
|
||||
isElected={stakingOverview && stakingOverview.currentElected.some((accountId): boolean => accountId.eq(address))}
|
||||
isFavorite={favorites.includes(address)}
|
||||
lastAuthors={lastAuthors}
|
||||
key={address.toString()}
|
||||
key={address}
|
||||
myAccounts={myAccounts}
|
||||
onFavorite={onFavorite}
|
||||
points={
|
||||
stakingOverview && pointIndexes && pointIndexes[index] !== -1
|
||||
? stakingOverview.eraPoints.individual[pointIndexes[index]]
|
||||
@ -48,28 +53,55 @@ function renderColumn (myAccounts: string[], addresses: AccountId[] | string[],
|
||||
));
|
||||
}
|
||||
|
||||
function filterAccounts (list: string[] = [], without: AccountId[] | string[]): string[] {
|
||||
return list.filter((accountId): boolean => !without.includes(accountId as any));
|
||||
function filterAccounts (list: string[] = [], without: string[], favorites: string[]): string[] {
|
||||
return list
|
||||
.filter((accountId): boolean => !without.includes(accountId as any))
|
||||
.sort((a, b): number => {
|
||||
const aIdx = favorites.includes(a);
|
||||
const bIdx = favorites.includes(b);
|
||||
|
||||
return aIdx === bIdx
|
||||
? 0
|
||||
: aIdx
|
||||
? -1
|
||||
: 1;
|
||||
});
|
||||
}
|
||||
|
||||
function accountsToString (accounts: AccountId[]): string[] {
|
||||
return accounts.map((accountId): string => accountId.toString());
|
||||
}
|
||||
|
||||
function CurrentList (props: Props): React.ReactElement<Props> {
|
||||
const { isSubstrateV2 } = useContext(ApiContext);
|
||||
const [favorites, setFavorites] = useState<string[]>(store.get(STORE_FAVS, []));
|
||||
const [filter, setFilter] = useState<ValidatorFilter>('all');
|
||||
const [myAccounts] = useState(keyring.getAccounts().map(({ address }): string => address));
|
||||
const [{ electedFiltered, nextFiltered, pointIndexes }, setFiltered] = useState<{ electedFiltered: string[]; nextFiltered: string[]; pointIndexes: number[] }>({ electedFiltered: [], nextFiltered: [], pointIndexes: [] });
|
||||
const [{ elected, pointIndexes, validators, waiting }, setFiltered] = useState<{ elected: string[]; pointIndexes: number[]; validators: string[]; waiting: string[] }>({ elected: [], pointIndexes: [], validators: [], waiting: [] });
|
||||
const { next, stakingOverview, t } = props;
|
||||
|
||||
useEffect((): void => {
|
||||
if (stakingOverview) {
|
||||
const elected = stakingOverview.currentElected.map((accountId): string => accountId.toString());
|
||||
const validators = filterAccounts(accountsToString(stakingOverview.validators), [], favorites);
|
||||
const elected = isSubstrateV2 ? filterAccounts(accountsToString(stakingOverview.currentElected), validators, favorites) : [];
|
||||
|
||||
setFiltered({
|
||||
electedFiltered: isSubstrateV2 ? filterAccounts(elected, stakingOverview.validators) : [],
|
||||
nextFiltered: filterAccounts(next, elected),
|
||||
pointIndexes: stakingOverview.validators.map((validator): number => elected.indexOf(validator.toString()))
|
||||
elected,
|
||||
pointIndexes: validators.map((validator): number => stakingOverview.currentElected.indexOf(validator as any)),
|
||||
validators,
|
||||
waiting: filterAccounts(next, elected, favorites)
|
||||
});
|
||||
}
|
||||
}, [next, stakingOverview]);
|
||||
}, [favorites, next, stakingOverview]);
|
||||
|
||||
const _onFavorite = (accountId: string): void => {
|
||||
const newFavs = favorites.includes(accountId)
|
||||
? favorites.filter((thisOne): boolean => thisOne !== accountId)
|
||||
: [...favorites, accountId];
|
||||
|
||||
store.set(STORE_FAVS, newFavs);
|
||||
setFavorites(newFavs);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -94,16 +126,16 @@ function CurrentList (props: Props): React.ReactElement<Props> {
|
||||
emptyText={t('No addresses found')}
|
||||
headerText={t('validators')}
|
||||
>
|
||||
{stakingOverview && renderColumn(myAccounts, stakingOverview.validators, t('validator'), true, filter, props, pointIndexes)}
|
||||
{stakingOverview && renderColumn(myAccounts, favorites, validators, t('validator'), true, filter, _onFavorite, props, pointIndexes)}
|
||||
</Column>
|
||||
<Column
|
||||
emptyText={t('No addresses found')}
|
||||
headerText={t('next up')}
|
||||
>
|
||||
{(electedFiltered.length !== 0 || nextFiltered.length !== 0) && (
|
||||
{(elected.length !== 0 || waiting.length !== 0) && (
|
||||
<>
|
||||
{renderColumn(myAccounts, electedFiltered, t('intention'), false, filter, props)}
|
||||
{renderColumn(myAccounts, nextFiltered, t('intention'), false, filter, props)}
|
||||
{renderColumn(myAccounts, favorites, elected, t('intention'), false, filter, _onFavorite, props)}
|
||||
{renderColumn(myAccounts, favorites, waiting, t('intention'), false, filter, _onFavorite, props)}
|
||||
</>
|
||||
)}
|
||||
</Column>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user