From 366c8804d091143f101cc7ad888ade18ad74fce3 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sun, 29 Nov 2020 12:56:13 +0100 Subject: [PATCH] Cleanup targets pre-filtering on sort (#4098) --- .../src/{Ledgend.tsx => Legend.tsx} | 4 +- .../page-staking/src/Overview/CurrentList.tsx | 6 +- .../page-staking/src/Targets/Validator.tsx | 11 +- packages/page-staking/src/Targets/index.tsx | 205 ++++++++++-------- packages/react-components/src/Table/index.tsx | 6 +- 5 files changed, 121 insertions(+), 111 deletions(-) rename packages/page-staking/src/{Ledgend.tsx => Legend.tsx} (92%) diff --git a/packages/page-staking/src/Ledgend.tsx b/packages/page-staking/src/Legend.tsx similarity index 92% rename from packages/page-staking/src/Ledgend.tsx rename to packages/page-staking/src/Legend.tsx index 306109baa9..94ff20efe9 100644 --- a/packages/page-staking/src/Ledgend.tsx +++ b/packages/page-staking/src/Legend.tsx @@ -11,7 +11,7 @@ interface Props { className?: string; } -function Ledgend ({ className }: Props): React.ReactElement { +function Legend ({ className }: Props): React.ReactElement { const { t } = useTranslation(); return ( @@ -62,7 +62,7 @@ function Ledgend ({ className }: Props): React.ReactElement { ); } -export default React.memo(styled(Ledgend)` +export default React.memo(styled(Legend)` font-size: 0.85rem; padding: 1rem 0.5rem; text-align: center; diff --git a/packages/page-staking/src/Overview/CurrentList.tsx b/packages/page-staking/src/Overview/CurrentList.tsx index dca624da1d..e9a78909fc 100644 --- a/packages/page-staking/src/Overview/CurrentList.tsx +++ b/packages/page-staking/src/Overview/CurrentList.tsx @@ -13,7 +13,7 @@ import { useApi, useCall, useLoadingDelay } from '@polkadot/react-hooks'; import { BlockAuthorsContext } from '@polkadot/react-query'; import Filtering from '../Filtering'; -import Ledgend from '../Ledgend'; +import Legend from '../Legend'; import { useTranslation } from '../translate'; import Address from './Address'; @@ -180,7 +180,7 @@ function CurrentList ({ favorites, hasQueries, isIntentions, next, stakingOvervi /> } header={headerWaitingRef.current} - ledgend={} + legend={} > {isLoading ? undefined : _renderRows(elected, false).concat(_renderRows(waiting, false))} @@ -197,7 +197,7 @@ function CurrentList ({ favorites, hasQueries, isIntentions, next, stakingOvervi /> } header={headerActiveRef.current} - ledgend={} + legend={} > {isLoading ? undefined : _renderRows(validators, true)} diff --git a/packages/page-staking/src/Targets/Validator.tsx b/packages/page-staking/src/Targets/Validator.tsx index 79d12c0c5e..6bdfbd2646 100644 --- a/packages/page-staking/src/Targets/Validator.tsx +++ b/packages/page-staking/src/Targets/Validator.tsx @@ -28,10 +28,7 @@ interface Props { isSelected: boolean; toggleFavorite: (accountId: string) => void; toggleSelected: (accountId: string) => void; - withElected: boolean; withIdentity: boolean; - withoutComm: boolean; - withoutOver: boolean; } function checkIdentity (api: ApiPromise, accountInfo: DeriveAccountInfo): [boolean, string | null] { @@ -63,7 +60,7 @@ function checkIdentity (api: ApiPromise, accountInfo: DeriveAccountInfo): [boole return [hasIdentity, parentId]; } -function Validator ({ allSlashes, canSelect, filterName, info, isNominated, isSelected, toggleFavorite, toggleSelected, withElected, withIdentity, withoutComm, withoutOver }: Props): React.ReactElement | null { +function Validator ({ allSlashes, canSelect, filterName, info, isNominated, isSelected, toggleFavorite, toggleSelected, withIdentity }: Props): React.ReactElement | null { const { t } = useTranslation(); const { api } = useApi(); const accountInfo = useCall(api.derive.accounts.info, [info.accountId]); @@ -101,11 +98,7 @@ function Validator ({ allSlashes, canSelect, filterName, info, isNominated, isSe [info.key, toggleSelected] ); - const nonToggled = (withElected && !info.isElected) || - (withoutComm && info.commissionPer >= 20) || - (withoutOver && api.consts.staking?.maxNominatorRewardedPerValidator?.lten(info.numNominators)); - - if (!isVisible || (!info.isFavorite && nonToggled)) { + if (!isVisible) { return null; } diff --git a/packages/page-staking/src/Targets/index.tsx b/packages/page-staking/src/Targets/index.tsx index 45c287971c..e0e00b5486 100644 --- a/packages/page-staking/src/Targets/index.tsx +++ b/packages/page-staking/src/Targets/index.tsx @@ -13,7 +13,7 @@ import { useApi, useAvailableSlashes } from '@polkadot/react-hooks'; import ElectionBanner from '../ElectionBanner'; import Filtering from '../Filtering'; -import Ledgend from '../Ledgend'; +import Legend from '../Legend'; import { MAX_NOMINATIONS } from '../constants'; import { useTranslation } from '../translate'; import Nominate from './Nominate'; @@ -31,6 +31,8 @@ interface Props { } interface Flags { + maxPaid: BN | undefined; + withElected: boolean; withGroup: boolean; withIdentity: boolean; withoutComm: boolean; @@ -46,18 +48,27 @@ const CLASSES: Record = { rankBondOther: 'media--1600', rankNumNominators: 'media--1200' }; +const MAX_COMM_FILTER = 20; +const SORT_KEYS = ['rankNumNominators', 'rankComm', 'rankBondTotal', 'rankBondOwn', 'rankBondOther', 'rankOverall']; -function sort (sortBy: TargetSortBy, sortFromMax: boolean, validators: ValidatorInfo[]): number[] { - return [...Array(validators.length).keys()] - .sort((a, b) => - sortFromMax - ? validators[a][sortBy] - validators[b][sortBy] - : validators[b][sortBy] - validators[a][sortBy] +function isIncluded ({ commissionPer, hasIdentity, isElected, isFavorite, numNominators }: ValidatorInfo, { maxPaid, withElected, withIdentity, withoutComm, withoutOver }: Flags): boolean { + return isFavorite || ( + (!withIdentity || hasIdentity) && + (!withElected || isElected) && + (!withoutComm || commissionPer < MAX_COMM_FILTER) && + (!withoutOver || !maxPaid || maxPaid.gtn(numNominators)) + ); +} + +function sort (sortBy: TargetSortBy, sortFromMax: boolean, validators: ValidatorInfo[]): ValidatorInfo[] { + return validators + .sort((a, b) => sortFromMax + ? a[sortBy] - b[sortBy] + : b[sortBy] - a[sortBy] ) - .sort((a, b) => - validators[a].isFavorite === validators[b].isFavorite - ? 0 - : (validators[a].isFavorite ? -1 : 1) + .sort((a, b) => a.isFavorite === b.isFavorite + ? 0 + : (a.isFavorite ? -1 : 1) ); } @@ -73,23 +84,18 @@ function extractNominees (ownNominators: StakerState[] = []): string[] { return myNominees; } -function selectProfitable (maxPaid: BN |undefined, list: ValidatorInfo[], { withGroup, withIdentity, withoutComm, withoutOver }: Flags): string[] { +function selectProfitable (list: ValidatorInfo[], flags: Flags): string[] { const parentIds: string[] = []; const result: string[] = []; + const { withGroup } = flags; for (let i = 0; i < list.length && result.length < MAX_NOMINATIONS; i++) { - const { commissionPer, hasIdentity, isElected, isFavorite, key, numNominators, parentId, stakedReturnCmp } = list[i]; + const { isFavorite, key, parentId, stakedReturnCmp } = list[i]; - if ( - (!withIdentity || hasIdentity) && - (isElected || isFavorite) && - stakedReturnCmp && - (isFavorite || ( - (!withGroup || !parentId || !parentIds.includes(parentId)) && - (!withoutComm || commissionPer < 20) && - (!withoutOver || !maxPaid || maxPaid.gtn(numNominators)) - )) - ) { + if (isFavorite || ( + (stakedReturnCmp > 0) && + (!withGroup || !parentId || !parentIds.includes(parentId)) + )) { result.push(key); parentId && parentIds.push(parentId); } @@ -111,14 +117,42 @@ function Targets ({ className = '', isInElection, ownStashes, targets: { avgStak const [withoutComm, setWithoutComm] = useState(true); const [withoutOver, setWithoutOver] = useState(true); const [{ sortBy, sortFromMax }, setSortBy] = useState({ sortBy: 'rankOverall', sortFromMax: true }); - const [sorted, setSorted] = useState(); + const [sorted, setSorted] = useState(); + + const labelsRef = useRef({ + rankBondOther: t('other stake'), + rankBondOwn: t('own stake'), + rankBondTotal: t('total stake'), + rankComm: t('comm.'), + rankNumNominators: t('nominators'), + rankOverall: t('return') + }); + + const flags = useMemo( + () => ({ + maxPaid: api.consts.staking?.maxNominatorRewardedPerValidator, + withElected, + withGroup, + withIdentity, + withoutComm, + withoutOver + }), + [api, withElected, withGroup, withIdentity, withoutComm, withoutOver] + ); + + const filtered = useMemo( + () => validators && validators.filter((info) => isIncluded(info, flags)), + [flags, validators] + ); // We are using an effect here to get this async. Sorting will have a double-render, however it allows // the page to immediately display (with loading), whereas useMemo would have a laggy interface // (the same applies for changing the sort order, state here is more effective) useEffect((): void => { - validators && setSorted(sort(sortBy, sortFromMax, validators)); - }, [sortBy, sortFromMax, validators]); + filtered && setSorted( + sort(sortBy, sortFromMax, filtered) + ); + }, [filtered, sortBy, sortFromMax]); const myNominees = useMemo( () => extractNominees(ownNominators), @@ -138,80 +172,66 @@ function Targets ({ className = '', isInElection, ownStashes, targets: { avgStak const _toggleSelected = useCallback( (address: string) => setSelected( selected.includes(address) - ? selected.filter((accountId): boolean => address !== accountId) + ? selected.filter((accountId) => address !== accountId) : [...selected, address] ), [selected] ); const _selectProfitable = useCallback( - () => setSelected(selectProfitable( - api.consts.staking?.maxNominatorRewardedPerValidator, - validators || [], - { withGroup, withIdentity, withoutComm, withoutOver } - )), - [api, validators, withGroup, withIdentity, withoutComm, withoutOver] + () => filtered && setSelected( + selectProfitable(filtered, flags) + ), + [filtered, flags] ); - const labelsRef = useRef({ - rankBondOther: t('other stake'), - rankBondOwn: t('own stake'), - rankBondTotal: t('total stake'), - rankComm: t('comm.'), - rankNumNominators: t('nominators'), - rankOverall: t('return') - }); - const header = useMemo(() => [ [t('validators'), 'start', 3], - ...(['rankNumNominators', 'rankComm', 'rankBondTotal', 'rankBondOwn', 'rankBondOther', 'rankOverall'] as (keyof typeof labelsRef.current)[]) - .map((header) => [ - <>{labelsRef.current[header]}, - `${sorted ? `isClickable ${sortBy === header ? 'highlight--border' : ''} number` : 'number'} ${CLASSES[header] || ''}`, - 1, - () => _sort(header as 'rankComm') - ]), + ...(SORT_KEYS as (keyof typeof labelsRef.current)[]).map((header) => [ + <>{labelsRef.current[header]}, + `${sorted ? `isClickable ${sortBy === header ? 'highlight--border' : ''} number` : 'number'} ${CLASSES[header] || ''}`, + 1, + () => _sort(header as 'rankComm') + ]), [], [] ], [_sort, labelsRef, sortBy, sorted, sortFromMax, t]); const filter = useMemo(() => ( - sorted && ( -
- - ('limit single operator exposure')} - onChange={setWithGroup} - value={withGroup} - /> - ('no 20%+ comm')} - onChange={setWithoutComm} - value={withoutComm} - /> - ('no oversubscribed')} - onChange={setWithoutOver} - value={withoutOver} - /> - ('only elected')} - onChange={setWithElected} - value={withElected} - /> - -
- ) - ), [nameFilter, sorted, t, withElected, withGroup, withIdentity, withoutComm, withoutOver]); +
+ + ('limit single operator exposure')} + onChange={setWithGroup} + value={withGroup} + /> + ('no {{maxComm}}%+ comm', { replace: { maxComm: MAX_COMM_FILTER } })} + onChange={setWithoutComm} + value={withoutComm} + /> + ('no oversubscribed')} + onChange={setWithoutOver} + value={withoutOver} + /> + ('only elected')} + onChange={setWithElected} + value={withElected} + /> + +
+ ), [nameFilter, t, withElected, withGroup, withIdentity, withoutComm, withoutOver]); return (
@@ -243,23 +263,20 @@ function Targets ({ className = '', isInElection, ownStashes, targets: { avgStak empty={sorted && t('No active validators to check')} filter={filter} header={header} - ledgend={} + legend={} > - {validators && sorted && (validators.length === sorted.length) && sorted.map((index): React.ReactNode => + {sorted?.map((info): React.ReactNode => )} diff --git a/packages/react-components/src/Table/index.tsx b/packages/react-components/src/Table/index.tsx index cda47b9ba8..5274f6d760 100644 --- a/packages/react-components/src/Table/index.tsx +++ b/packages/react-components/src/Table/index.tsx @@ -17,7 +17,7 @@ interface TableProps { footer?: React.ReactNode; header?: [React.ReactNode?, string?, number?, (() => void)?][]; isFixed?: boolean; - ledgend?: React.ReactNode; + legend?: React.ReactNode; } function extractKids (children: React.ReactNode): [boolean, React.ReactNode] { @@ -31,12 +31,12 @@ function extractKids (children: React.ReactNode): [boolean, React.ReactNode] { return [isEmpty, isEmpty ? null : kids]; } -function Table ({ children, className = '', empty, emptySpinner, filter, footer, header, isFixed, ledgend }: TableProps): React.ReactElement { +function Table ({ children, className = '', empty, emptySpinner, filter, footer, header, isFixed, legend }: TableProps): React.ReactElement { const [isEmpty, kids] = extractKids(children); return (
- {ledgend} + {legend}