From 498a4818ffbbb0bb736b89a0ad40d6d861686acf Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 2 Apr 2021 22:19:52 +0300 Subject: [PATCH] Sort parachain lists with known first (#5011) * Sort parachain lists with known first * leases re-sort * Adjust sorting --- .../page-parachains/src/Auctions/Auction.tsx | 2 +- .../page-parachains/src/Crowdloan/Funds.tsx | 55 ++++++++---- .../src/Overview/Parachains.tsx | 37 +++++--- .../page-parachains/src/Parathreads/index.tsx | 85 +++++++++++-------- packages/page-parachains/src/types.ts | 1 + packages/page-parachains/src/useFunds.ts | 1 + .../page-parachains/src/useWinningData.ts | 1 + packages/react-hooks/src/index.ts | 2 +- packages/react-hooks/src/useParaEndpoints.ts | 36 ++++++-- 9 files changed, 150 insertions(+), 70 deletions(-) diff --git a/packages/page-parachains/src/Auctions/Auction.tsx b/packages/page-parachains/src/Auctions/Auction.tsx index 70fcf87825..5ca8013395 100644 --- a/packages/page-parachains/src/Auctions/Auction.tsx +++ b/packages/page-parachains/src/Auctions/Auction.tsx @@ -99,7 +99,7 @@ function Auction ({ auctionInfo, campaigns, className, winningData }: Props): Re blockNumber={blockNumber} isFirst={index === 0} isLatest={round === 0} - key={`${blockNumber.toString()}:${index}`} + key={`${blockNumber.toString()}:${value.key}`} value={value} /> ))} diff --git a/packages/page-parachains/src/Crowdloan/Funds.tsx b/packages/page-parachains/src/Crowdloan/Funds.tsx index a0b3efd385..6d0f1a4926 100644 --- a/packages/page-parachains/src/Crowdloan/Funds.tsx +++ b/packages/page-parachains/src/Crowdloan/Funds.tsx @@ -2,11 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 import type BN from 'bn.js'; +import type { ParaId } from '@polkadot/types/interfaces'; import type { Campaign, LeasePeriod } from '../types'; import React, { useMemo, useRef } from 'react'; import { Table } from '@polkadot/react-components'; +import { useIsParasLinked } from '@polkadot/react-hooks'; import { useTranslation } from '../translate'; import Fund from './Fund'; @@ -18,19 +20,45 @@ interface Props { value: Campaign[] | null; } -function extractLists (value: Campaign[] | null, leasePeriod?: LeasePeriod): [Campaign[] | null, Campaign[] | null] { +function extractLists (value: Campaign[] | null, leasePeriod?: LeasePeriod): [Campaign[] | null, Campaign[] | null, ParaId[] | null] { const currentPeriod = leasePeriod?.currentPeriod; + let active: Campaign[] | null = null; + let ended: Campaign[] | null = null; + let allIds: ParaId[] | null = null; - return value && currentPeriod - ? [ - value.filter(({ firstSlot, isCapped, isEnded, isWinner }) => !(isCapped || isEnded || isWinner) && currentPeriod.lte(firstSlot)), - value.filter(({ firstSlot, isCapped, isEnded, isWinner }) => (isCapped || isEnded || isWinner) || currentPeriod.gt(firstSlot)) - ] - : [null, null]; + if (value && currentPeriod) { + active = value.filter(({ firstSlot, isCapped, isEnded, isWinner }) => !(isCapped || isEnded || isWinner) && currentPeriod.lte(firstSlot)); + ended = value.filter(({ firstSlot, isCapped, isEnded, isWinner }) => (isCapped || isEnded || isWinner) || currentPeriod.gt(firstSlot)); + allIds = value.map(({ paraId }) => paraId); + } + + return [active, ended, allIds]; +} + +function sortList (hasLinksMap: Record, list?: Campaign[] | null): Campaign[] | null | undefined { + return list?.sort(({ key: a }, { key: b }): number => { + const aKnown = hasLinksMap[a] || false; + const bKnown = hasLinksMap[b] || false; + + return aKnown === bKnown + ? 0 + : aKnown + ? -1 + : 1; + }); } function Funds ({ bestNumber, className, leasePeriod, value }: Props): React.ReactElement { const { t } = useTranslation(); + const [active, ended, allIds] = useMemo( + () => extractLists(value, leasePeriod), + [leasePeriod, value] + ); + const hasLinksMap = useIsParasLinked(allIds); + const [activeSorted, endedSorted] = useMemo( + () => [sortList(hasLinksMap, active), sortList(hasLinksMap, ended)], + [active, ended, hasLinksMap] + ); const headerActiveRef = useRef([ [t('ongoing'), 'start', 2], @@ -57,19 +85,14 @@ function Funds ({ bestNumber, className, leasePeriod, value }: Props): React.Rea [] ]); - const [active, ended] = useMemo( - () => extractLists(value, leasePeriod), - [leasePeriod, value] - ); - return ( <> ('No active campaigns found')} + empty={value && activeSorted && t('No active campaigns found')} header={headerActiveRef.current} > - {active?.map((fund) => ( + {activeSorted?.map((fund) => (
('No completed campaigns found')} + empty={value && endedSorted && t('No completed campaigns found')} header={headedEndedRef.current} > - {ended?.map((fund) => ( + {endedSorted?.map((fund) => ( { return activeIndices && validators && validatorGroups && ids && ids.length === validatorGroups.length - ? validatorGroups.map((indices) => - indices + ? validatorGroups.reduce((all: Record, indices, index) => ({ + ...all, + [ids[index].toString()]: indices .map((indexActive) => [indexActive, activeIndices[indexActive.toNumber()]]) .filter(([, a]) => a) .map(([indexActive, indexValidator]) => ({ @@ -72,8 +73,8 @@ function mapValidators (ids: ParaId[] | undefined, validators: AccountId[] | nul indexValidator, validatorId: validators[indexValidator.toNumber()] })) - ) - : []; + }), {}) + : {}; } function extractEvents (api: ApiPromise, lastBlock: SignedBlockExtended, prev: LastEvents): LastEvents { @@ -125,6 +126,21 @@ function extractActions (actionsQueue: QueuedAction[], knownIds?: [ParaId, strin : {}; } +function extractIds (hasLinksMap: Record, ids?: ParaId[]): [ParaId, string][] | undefined { + return ids + ?.map((id): [ParaId, string] => [id, id.toString()]) + .sort(([aId, aIds], [bId, bIds]): number => { + const aKnown = hasLinksMap[aIds] || false; + const bKnown = hasLinksMap[bIds] || false; + + return aKnown === bKnown + ? aId.cmp(bId) + : aKnown + ? -1 + : 1; + }); +} + function Parachains ({ actionsQueue, ids, leasePeriod, scheduled }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); @@ -137,6 +153,7 @@ function Parachains ({ actionsQueue, ids, leasePeriod, scheduled }: Props): Reac api.query.shared?.activeValidatorIndices ], optionsMulti); const hrmp = useHrmp(); + const hasLinksMap = useIsParasLinked(ids); const headerRef = useRef([ [t('parachains'), 'start', 2], @@ -163,8 +180,8 @@ function Parachains ({ actionsQueue, ids, leasePeriod, scheduled }: Props): Reac ); const knownIds = useMemo( - () => ids?.map((id): [ParaId, string] => [id, id.toString()]), - [ids] + () => extractIds(hasLinksMap, ids), + [ids, hasLinksMap] ); const nextActions = useMemo( @@ -183,7 +200,7 @@ function Parachains ({ actionsQueue, ids, leasePeriod, scheduled }: Props): Reac empty={knownIds && t('There are no registered parachains')} header={headerRef.current} > - {knownIds?.map(([id, key], index): React.ReactNode => ( + {knownIds?.map(([id, key]): React.ReactNode => ( ))}
diff --git a/packages/page-parachains/src/Parathreads/index.tsx b/packages/page-parachains/src/Parathreads/index.tsx index 67692f8e87..a127a7b45f 100644 --- a/packages/page-parachains/src/Parathreads/index.tsx +++ b/packages/page-parachains/src/Parathreads/index.tsx @@ -6,10 +6,10 @@ import type { AccountId, BalanceOf, ParaId } from '@polkadot/types/interfaces'; import type { ITuple } from '@polkadot/types/types'; import type { LeaseInfo, LeasePeriod, QueuedAction } from '../types'; -import React, { useRef } from 'react'; +import React, { useCallback, useRef } from 'react'; import { Table } from '@polkadot/react-components'; -import { useApi, useCall } from '@polkadot/react-hooks'; +import { useApi, useCall, useIsParasLinked } from '@polkadot/react-hooks'; import { useTranslation } from '../translate'; import Actions from './Actions'; @@ -24,47 +24,60 @@ interface Props { type ParaMap = [ParaId, LeaseInfo[]][]; -const optLeases = { - transform: ([[paraIds], leases]: [[ParaId[]], Option>[][]]): ParaMap => - paraIds - .reduce((all: ParaMap, id, index): ParaMap => { - all.push([ - id, - leases[index] - .map((optLease, period): LeaseInfo | null => { - if (optLease.isNone) { - return null; - } +function extractParaMap (hasLinksMap: Record, paraIds: ParaId[], leases: Option>[][]): ParaMap { + return paraIds + .reduce((all: ParaMap, id, index): ParaMap => { + all.push([ + id, + leases[index] + .map((optLease, period): LeaseInfo | null => { + if (optLease.isNone) { + return null; + } - const [accountId, balance] = optLease.unwrap(); + const [accountId, balance] = optLease.unwrap(); - return { - accountId, - balance, - period - }; - }) - .filter((item): item is LeaseInfo => !!item) - ]); + return { + accountId, + balance, + period + }; + }) + .filter((item): item is LeaseInfo => !!item) + ]); - return all; - }, []) - .sort(([aId, aLeases], [bId, bLeases]) => - aLeases.length && bLeases.length - ? (aLeases[0].period - bLeases[0].period) || aId.cmp(bId) - : aLeases.length - ? -1 - : bLeases.length - ? 1 - : aId.cmp(bId) - ), - withParamsTransform: true -}; + return all; + }, []) + .sort(([aId, aLeases], [bId, bLeases]): number => { + const aKnown = hasLinksMap[aId.toString()] || false; + const bKnown = hasLinksMap[bId.toString()] || false; + + return aLeases.length && bLeases.length + ? (aLeases[0].period - bLeases[0].period) || aId.cmp(bId) + : aLeases.length + ? -1 + : bLeases.length + ? 1 + : aKnown === bKnown + ? aId.cmp(bId) + : aKnown + ? -1 + : 1; + }); +} function Parathreads ({ actionsQueue, className, ids, leasePeriod }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); - const leaseMap = useCall(ids && api.query.slots.leases.multi, [ids], optLeases); + const hasLinksMap = useIsParasLinked(ids); + const leaseMap = useCall(ids && api.query.slots.leases.multi, [ids], { + transform: useCallback( + ([[paraIds], leases]: [[ParaId[]], Option>[][]]): ParaMap => + extractParaMap(hasLinksMap, paraIds, leases), + [hasLinksMap] + ), + withParamsTransform: true + }); const headerRef = useRef([ [t('parathreads'), 'start', 3], diff --git a/packages/page-parachains/src/types.ts b/packages/page-parachains/src/types.ts index f902368911..2a0eaeb67f 100644 --- a/packages/page-parachains/src/types.ts +++ b/packages/page-parachains/src/types.ts @@ -86,6 +86,7 @@ export interface WinnerData { accountId: string; firstSlot: BN; isCrowdloan: boolean; + key: string; lastSlot: BN; paraId: ParaId; value: BN; diff --git a/packages/page-parachains/src/useFunds.ts b/packages/page-parachains/src/useFunds.ts index f5c9bf202e..207bdde6d7 100644 --- a/packages/page-parachains/src/useFunds.ts +++ b/packages/page-parachains/src/useFunds.ts @@ -138,6 +138,7 @@ const optFundMulti = { firstSlot: info.firstSlot, info, isCrowdloan: true, + key: paraId.toString(), lastSlot: info.lastSlot, paraId, value: info.raised diff --git a/packages/page-parachains/src/useWinningData.ts b/packages/page-parachains/src/useWinningData.ts index d7a90bac08..d0f1ffb4ce 100644 --- a/packages/page-parachains/src/useWinningData.ts +++ b/packages/page-parachains/src/useWinningData.ts @@ -41,6 +41,7 @@ function extractWinners (auctionInfo: AuctionInfo, optData: Option) accountId: accountId.toString(), firstSlot: period.addn(first), isCrowdloan: u8aEq(CROWD_PREFIX, accountId.subarray(0, CROWD_PREFIX.length)), + key: paraId.toString(), lastSlot: period.addn(last), paraId, value diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index 7f006372f6..937eb65d5d 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -33,7 +33,7 @@ export { useOwnEraRewards } from './useOwnEraRewards'; export { useOwnStashes, useOwnStashIds } from './useOwnStashes'; export { useOwnStashInfos } from './useOwnStashInfos'; export { useParaApi } from './useParaApi'; -export { useParaEndpoints } from './useParaEndpoints'; +export { useIsParasLinked, useParaEndpoints } from './useParaEndpoints'; export { usePassword } from './usePassword'; export { useRegistrars } from './useRegistrars'; export { useSavedFlags } from './useSavedFlags'; diff --git a/packages/react-hooks/src/useParaEndpoints.ts b/packages/react-hooks/src/useParaEndpoints.ts index 12b6b9a048..ff5a66553f 100644 --- a/packages/react-hooks/src/useParaEndpoints.ts +++ b/packages/react-hooks/src/useParaEndpoints.ts @@ -11,19 +11,43 @@ import { bnToBn } from '@polkadot/util'; import { useApi } from './useApi'; -function extractResults (genesisHash: string, paraId: BN | number): LinkOption[] { +function extractRelayEndpoints (genesisHash: string): LinkOption[] { + return createWsEndpoints((key: string, value: string | undefined) => value || key).filter(({ genesisHashRelay }) => + genesisHash === genesisHashRelay + ); +} + +function extractParaEndpoints (allEndpoints: LinkOption[], paraId: BN | number): LinkOption[] { const numId = bnToBn(paraId).toNumber(); - return createWsEndpoints((key: string, value: string | undefined) => value || key).filter(({ genesisHashRelay, paraId }) => - paraId === numId && genesisHashRelay === genesisHash + return allEndpoints.filter(({ paraId }) => paraId === numId); +} + +function useRelayEndpoints (): LinkOption[] { + const { api } = useApi(); + + return useMemo( + () => extractRelayEndpoints(api.genesisHash.toHex()), + [api] ); } export function useParaEndpoints (paraId: BN | number): LinkOption[] { - const { api } = useApi(); + const endpoints = useRelayEndpoints(); return useMemo( - () => extractResults(api.genesisHash.toHex(), paraId), - [api, paraId] + () => extractParaEndpoints(endpoints, paraId), + [endpoints, paraId] + ); +} + +export function useIsParasLinked (ids?: (BN | number)[] | null): Record { + const endpoints = useRelayEndpoints(); + + return useMemo( + () => ids + ? ids.reduce((all: Record, id) => ({ ...all, [id.toString()]: extractParaEndpoints(endpoints, id).length !== 0 }), {}) + : {}, + [endpoints, ids] ); }