Skip to content

Commit

Permalink
Add staking favorites (top of list) (#1866)
Browse files Browse the repository at this point in the history
  • Loading branch information
jacogr authored Nov 11, 2019
1 parent 6ddfaf0 commit 2a52609
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 20 deletions.
35 changes: 31 additions & 4 deletions packages/app-staking/src/Overview/Address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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;
Expand All @@ -220,7 +248,6 @@ export default withMulti(
.blockNumberV2 {
display: inline-block;
margin-bottom: 0.75rem;
padding: 0.25rem 0;
&.isCurrent {
Expand Down
64 changes: 48 additions & 16 deletions packages/app-staking/src/Overview/CurrentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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]]
Expand All @@ -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>
Expand All @@ -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>
Expand Down

0 comments on commit 2a52609

Please sign in to comment.