Shared staking filtering (#3009)
This commit is contained in:
parent
3e610c431c
commit
f425edbceb
59
packages/page-staking/src/Filtering.tsx
Normal file
59
packages/page-staking/src/Filtering.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
// 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 React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Input, Toggle } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from './translate';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
nameFilter: string;
|
||||
setNameFilter: (value: string) => void;
|
||||
setWithIdentity: (value: boolean) => void;
|
||||
withIdentity: boolean;
|
||||
}
|
||||
|
||||
function Filtering ({ children, className, nameFilter, setNameFilter, setWithIdentity, withIdentity }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Input
|
||||
autoFocus
|
||||
isFull
|
||||
label={t<string>('filter by name, address or index')}
|
||||
onChange={setNameFilter}
|
||||
value={nameFilter}
|
||||
/>
|
||||
<div className='staking--optionsBar'>
|
||||
{children}
|
||||
{api.query.identity && (
|
||||
<Toggle
|
||||
className='staking--buttonToggle'
|
||||
label={t<string>('only with an identity')}
|
||||
onChange={setWithIdentity}
|
||||
value={withIdentity}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Filtering)`
|
||||
.staking--optionsBar {
|
||||
text-align: right;
|
||||
|
||||
.staking--buttonToggle {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
`);
|
||||
@ -7,12 +7,11 @@ import { DeriveAccountInfo, DeriveStakingQuery } from '@polkadot/api-derive/type
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import ApiPromise from '@polkadot/api/promise';
|
||||
import { AddressSmall, Icon } from '@polkadot/react-components';
|
||||
import { useAccounts, useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
|
||||
import { checkVisibility } from '../../util';
|
||||
import Favorite from './Favorite';
|
||||
import NominatedBy from './NominatedBy';
|
||||
import Status from './Status';
|
||||
@ -33,7 +32,7 @@ interface Props {
|
||||
onlineMessage?: boolean;
|
||||
points?: string;
|
||||
toggleFavorite: (accountId: string) => void;
|
||||
withoutName: boolean;
|
||||
withIdentity: boolean;
|
||||
}
|
||||
|
||||
interface StakingState {
|
||||
@ -72,39 +71,7 @@ function expandInfo ({ exposure, validatorPrefs }: DeriveStakingQuery): StakingS
|
||||
};
|
||||
}
|
||||
|
||||
function checkVisibility (api: ApiPromise, address: string, filterName: string, withoutName: boolean, accountInfo?: DeriveAccountInfo): boolean {
|
||||
let isVisible = false;
|
||||
const filterLower = filterName.toLowerCase();
|
||||
|
||||
if (filterLower || !withoutName) {
|
||||
if (accountInfo) {
|
||||
const { accountId, accountIndex, identity, nickname } = accountInfo;
|
||||
|
||||
if (withoutName && (accountId?.toString().includes(filterName) || accountIndex?.toString().includes(filterName))) {
|
||||
isVisible = true;
|
||||
} else if (api.query.identity && api.query.identity.identityOf) {
|
||||
isVisible = (!!identity?.display && identity.display.toLowerCase().includes(filterLower)) ||
|
||||
(!!identity?.displayParent && identity.displayParent.toLowerCase().includes(filterLower));
|
||||
} else if (nickname) {
|
||||
isVisible = nickname.toLowerCase().includes(filterLower);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isVisible) {
|
||||
const account = keyring.getAddress(address);
|
||||
|
||||
isVisible = account?.meta?.name
|
||||
? account.meta.name.toLowerCase().includes(filterLower)
|
||||
: false;
|
||||
}
|
||||
} else {
|
||||
isVisible = true;
|
||||
}
|
||||
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
function Address ({ address, className = '', filterName, hasQueries, isAuthor, isElected, isFavorite, isMain, lastBlock, nominatedBy, onlineCount, onlineMessage, points, toggleFavorite, withoutName }: Props): React.ReactElement<Props> | null {
|
||||
function Address ({ address, className = '', filterName, hasQueries, isAuthor, isElected, isFavorite, isMain, lastBlock, nominatedBy, onlineCount, onlineMessage, points, toggleFavorite, withIdentity }: Props): React.ReactElement<Props> | null {
|
||||
const { api } = useApi();
|
||||
const { allAccounts } = useAccounts();
|
||||
const accountInfo = useCall<DeriveAccountInfo>(api.derive.accounts.info, [address]);
|
||||
@ -119,9 +86,9 @@ function Address ({ address, className = '', filterName, hasQueries, isAuthor, i
|
||||
|
||||
useEffect((): void => {
|
||||
setIsVisible(
|
||||
checkVisibility(api, address, filterName, withoutName, accountInfo)
|
||||
checkVisibility(api, address, filterName, withIdentity, accountInfo)
|
||||
);
|
||||
}, [api, accountInfo, address, filterName, withoutName]);
|
||||
}, [api, accountInfo, address, filterName, withIdentity]);
|
||||
|
||||
useEffect((): void => {
|
||||
!isMain && setIsNominating(
|
||||
|
||||
@ -7,11 +7,12 @@ 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 { Table } 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 Filtering from '../Filtering';
|
||||
import { STORE_FAVS_BASE } from '../constants';
|
||||
import { useTranslation } from '../translate';
|
||||
import Address from './Address';
|
||||
@ -98,7 +99,7 @@ function CurrentList ({ hasQueries, isIntentions, next, stakingOverview }: Props
|
||||
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);
|
||||
const [withIdentity, setWithIdentity] = useState(false);
|
||||
|
||||
useEffect((): void => {
|
||||
stakingOverview && setFiltered(
|
||||
@ -129,32 +130,6 @@ function CurrentList ({ hasQueries, isIntentions, next, stakingOverview }: Props
|
||||
[]
|
||||
], [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 => (
|
||||
@ -173,17 +148,24 @@ function CurrentList ({ hasQueries, isIntentions, next, stakingOverview }: Props
|
||||
onlineMessage={recentlyOnline?.[address]?.hasMessage}
|
||||
points={eraPoints[address]}
|
||||
toggleFavorite={toggleFavorite}
|
||||
withoutName={withoutName}
|
||||
withIdentity={withIdentity}
|
||||
/>
|
||||
)),
|
||||
[byAuthor, eraPoints, hasQueries, lastBlockAuthors, nameFilter, nominatedBy, recentlyOnline, toggleFavorite, withoutName]
|
||||
[byAuthor, eraPoints, hasQueries, lastBlockAuthors, nameFilter, nominatedBy, recentlyOnline, toggleFavorite, withIdentity]
|
||||
);
|
||||
|
||||
return isIntentions
|
||||
? (
|
||||
<Table
|
||||
empty={waiting && t<string>('No waiting validators found')}
|
||||
filter={filter}
|
||||
filter={
|
||||
<Filtering
|
||||
nameFilter={nameFilter}
|
||||
setNameFilter={setNameFilter}
|
||||
setWithIdentity={setWithIdentity}
|
||||
withIdentity={withIdentity}
|
||||
/>
|
||||
}
|
||||
header={headerActive}
|
||||
>
|
||||
{_renderRows(elected, false).concat(..._renderRows(waiting, false))}
|
||||
@ -192,7 +174,14 @@ function CurrentList ({ hasQueries, isIntentions, next, stakingOverview }: Props
|
||||
: (
|
||||
<Table
|
||||
empty={validators && t<string>('No active validators found')}
|
||||
filter={filter}
|
||||
filter={
|
||||
<Filtering
|
||||
nameFilter={nameFilter}
|
||||
setNameFilter={setNameFilter}
|
||||
setWithIdentity={setWithIdentity}
|
||||
withIdentity={withIdentity}
|
||||
/>
|
||||
}
|
||||
header={headerWaiting}
|
||||
>
|
||||
{_renderRows(validators, true)}
|
||||
|
||||
@ -15,9 +15,11 @@ import keyring from '@polkadot/ui-keyring';
|
||||
|
||||
import MaxBadge from '../MaxBadge';
|
||||
import Favorite from '../Overview/Address/Favorite';
|
||||
import { checkVisibility } from '../util';
|
||||
|
||||
interface Props {
|
||||
canSelect: boolean;
|
||||
filterName: string;
|
||||
info: ValidatorInfo;
|
||||
isSelected: boolean;
|
||||
toggleFavorite: (accountId: string) => void;
|
||||
@ -26,39 +28,37 @@ interface Props {
|
||||
withIdentity: boolean;
|
||||
}
|
||||
|
||||
function checkVisibility (api: ApiPromise, accountInfo: DeriveAccountInfo): boolean {
|
||||
let isVisible = false;
|
||||
function checkIdentity (api: ApiPromise, accountInfo: DeriveAccountInfo): boolean {
|
||||
let hasIdentity = false;
|
||||
|
||||
const { accountId, identity, nickname } = accountInfo;
|
||||
|
||||
if (api.query.identity && api.query.identity.identityOf) {
|
||||
isVisible = !!(identity?.display && identity.display.toString());
|
||||
hasIdentity = !!(identity?.display && identity.display.toString());
|
||||
} else if (nickname) {
|
||||
isVisible = !!nickname.toString();
|
||||
hasIdentity = !!nickname.toString();
|
||||
}
|
||||
|
||||
if (!isVisible && accountId) {
|
||||
if (!hasIdentity && accountId) {
|
||||
const account = keyring.getAddress(accountId.toString());
|
||||
|
||||
isVisible = !!account?.meta?.name;
|
||||
hasIdentity = !!account?.meta?.name;
|
||||
}
|
||||
|
||||
return isVisible;
|
||||
return hasIdentity;
|
||||
}
|
||||
|
||||
function Validator ({ canSelect, info, isSelected, toggleFavorite, toggleSelected, withElected, withIdentity }: Props): React.ReactElement<Props> | null {
|
||||
function Validator ({ canSelect, filterName, info, isSelected, toggleFavorite, toggleSelected, withElected, withIdentity }: Props): React.ReactElement<Props> | null {
|
||||
const { api } = useApi();
|
||||
const accountInfo = useCall<DeriveAccountInfo>(api.derive.accounts.info, [info.accountId]);
|
||||
const [hasName, setHasName] = useState(true);
|
||||
const [isVisible, setVisibility] = useState(true);
|
||||
|
||||
useEffect((): void => {
|
||||
if (accountInfo) {
|
||||
const hasIdentity = checkVisibility(api, accountInfo);
|
||||
|
||||
info.hasIdentity = hasIdentity;
|
||||
setHasName(hasIdentity);
|
||||
info.hasIdentity = checkIdentity(api, accountInfo);
|
||||
setVisibility(checkVisibility(api, info.key, filterName, withIdentity, accountInfo));
|
||||
}
|
||||
}, [accountInfo, api, info]);
|
||||
}, [accountInfo, api, filterName, info, withIdentity]);
|
||||
|
||||
const _onQueryStats = useCallback(
|
||||
(): void => {
|
||||
@ -72,7 +72,7 @@ function Validator ({ canSelect, info, isSelected, toggleFavorite, toggleSelecte
|
||||
[info.key, toggleSelected]
|
||||
);
|
||||
|
||||
if ((withIdentity && !hasName) || (withElected && !info.isElected)) {
|
||||
if (!isVisible || (withElected && !info.isElected)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -9,8 +9,8 @@ import { SortedTargets, TargetSortBy, ValidatorInfo } from '../types';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Button, Icon, InputBalance, Table, Toggle } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
|
||||
import Filtering from '../Filtering';
|
||||
import { MAX_NOMINATIONS } from '../constants';
|
||||
import { useTranslation } from '../translate';
|
||||
import Nominate from './Nominate';
|
||||
@ -47,10 +47,10 @@ function sort (sortBy: TargetSortBy, sortFromMax: boolean, validators: Validator
|
||||
|
||||
function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward, nominators, setCalcWith, toggleFavorite, totalStaked, validators } }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const ownNominators = useOwnNominators(ownStashes);
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
const [sorted, setSorted] = useState<number[] | undefined>();
|
||||
const [nameFilter, setNameFilter] = useState<string>('');
|
||||
const [withElected, setWithElected] = useState(false);
|
||||
const [withIdentity, setWithIdentity] = useState(false);
|
||||
const [{ sortBy, sortFromMax }, setSortBy] = useState<SortState>({ sortBy: 'rankOverall', sortFromMax: true });
|
||||
@ -128,9 +128,22 @@ function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward,
|
||||
onChange={setCalcWith}
|
||||
value={calcWith}
|
||||
/>
|
||||
<Filtering
|
||||
nameFilter={nameFilter}
|
||||
setNameFilter={setNameFilter}
|
||||
setWithIdentity={setWithIdentity}
|
||||
withIdentity={withIdentity}
|
||||
>
|
||||
<Toggle
|
||||
className='staking--buttonToggle'
|
||||
label={t<string>('limit to elected')}
|
||||
onChange={setWithElected}
|
||||
value={withElected}
|
||||
/>
|
||||
</Filtering>
|
||||
</div>
|
||||
)
|
||||
), [calcWith, setCalcWith, sorted, t]);
|
||||
), [calcWith, setCalcWith, nameFilter, sorted, t, withElected, withIdentity]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
@ -141,22 +154,6 @@ function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward,
|
||||
totalStaked={totalStaked}
|
||||
/>
|
||||
<Button.Group>
|
||||
<span className='staking--optionsBar'>
|
||||
<Toggle
|
||||
className='staking--buttonToggle'
|
||||
label={t<string>('limit to elected')}
|
||||
onChange={setWithElected}
|
||||
value={withElected}
|
||||
/>
|
||||
{api.query.identity && (
|
||||
<Toggle
|
||||
className='staking--buttonToggle'
|
||||
label={t<string>('only with an identity')}
|
||||
onChange={setWithIdentity}
|
||||
value={withIdentity}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<Button
|
||||
icon='check'
|
||||
isDisabled={!validators?.length || !ownNominators?.length}
|
||||
@ -176,6 +173,7 @@ function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward,
|
||||
{validators && sorted && (validators.length === sorted.length) && sorted.map((index): React.ReactNode =>
|
||||
<Validator
|
||||
canSelect={selected.length < MAX_NOMINATIONS}
|
||||
filterName={nameFilter}
|
||||
info={validators[index]}
|
||||
isSelected={selected.includes(validators[index].key)}
|
||||
key={validators[index].key}
|
||||
|
||||
@ -171,14 +171,4 @@ export default React.memo(styled(StakingApp)`
|
||||
margin: 2.5rem auto;
|
||||
}
|
||||
}
|
||||
|
||||
.staking--optionsBar {
|
||||
text-align: right;
|
||||
|
||||
.staking--buttonToggle {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
40
packages/page-staking/src/util.ts
Normal file
40
packages/page-staking/src/util.ts
Normal file
@ -0,0 +1,40 @@
|
||||
// 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 { DeriveAccountInfo } from '@polkadot/api-derive/types';
|
||||
|
||||
import { ApiPromise } from '@polkadot/api';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
|
||||
export function checkVisibility (api: ApiPromise, address: string, filterName: string, withIdentity: boolean, accountInfo?: DeriveAccountInfo): boolean {
|
||||
let isVisible = false;
|
||||
const filterLower = filterName.toLowerCase();
|
||||
|
||||
if (filterLower || withIdentity) {
|
||||
if (accountInfo) {
|
||||
const { accountId, accountIndex, identity, nickname } = accountInfo;
|
||||
|
||||
if (!withIdentity && (accountId?.toString().includes(filterName) || accountIndex?.toString().includes(filterName))) {
|
||||
isVisible = true;
|
||||
} else if (api.query.identity && api.query.identity.identityOf) {
|
||||
isVisible = (!!identity?.display && identity.display.toLowerCase().includes(filterLower)) ||
|
||||
(!!identity?.displayParent && identity.displayParent.toLowerCase().includes(filterLower));
|
||||
} else if (nickname) {
|
||||
isVisible = nickname.toLowerCase().includes(filterLower);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isVisible) {
|
||||
const account = keyring.getAddress(address);
|
||||
|
||||
isVisible = account?.meta?.name
|
||||
? account.meta.name.toLowerCase().includes(filterLower)
|
||||
: false;
|
||||
}
|
||||
} else {
|
||||
isVisible = true;
|
||||
}
|
||||
|
||||
return isVisible;
|
||||
}
|
||||
@ -80,6 +80,10 @@ export default React.memo(styled(Table)`
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
th label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user