Split parathreads tab (#4997)

* Split parathreads tab

* Keep threads warm

* Remove extra ?.

* Type for id + leases
This commit is contained in:
Jaco Greeff 2021-04-01 16:58:27 +03:00 committed by GitHub
parent 4c95a929b4
commit 31448157eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 199 additions and 153 deletions

View File

@ -91,7 +91,7 @@ function Bid ({ auctionInfo, className, lastWinners, ownedIds }: Props): React.R
/>
<Modal.Columns hint={t<string>('The first and last lease period for this bid. The last lease period should be after the first and a maximum of 3 more than the first.')}>
<Dropdown
label={t<string>('bid period range (first period - last period)')}
label={t<string>('bid period range (first lease - last lease)')}
onChange={setRange}
options={rangeOpts}
value={range}
@ -99,7 +99,7 @@ function Bid ({ auctionInfo, className, lastWinners, ownedIds }: Props): React.R
</Modal.Columns>
<Modal.Columns hint={
<>
<p>{t<string>('The amount to to bid for this parachain period range.')}</p>
<p>{t<string>('The amount to to bid for this parachain lease period range.')}</p>
<p>{t<string>('The bid should be more than the current range winner to be accepted and influence the auction outcome.')}</p>
</>
}>

View File

@ -107,7 +107,7 @@ function Refund ({ allAccounts, className, myAccounts, paraId }: Props): React.R
<Button
icon='minus'
isDisabled={!hasAccounts}
label={t<string>('Withdraw')}
label={t<string>('Refund')}
onClick={toggleOpen}
/>
{isOpen && (

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import type { ParaLifecycle } from '@polkadot/types/interfaces';
import type { QueuedAction } from './types';
import type { QueuedAction } from '../types';
import React from 'react';

View File

@ -4,8 +4,8 @@
import type { Option, Vec } from '@polkadot/types';
import type { AccountId, BalanceOf, BlockNumber, CandidatePendingAvailability, HeadData, Header, HrmpChannel, HrmpChannelId, ParaId, ParaInfo, ParaLifecycle } from '@polkadot/types/interfaces';
import type { Codec, ITuple } from '@polkadot/types/types';
import type { LeasePeriod } from '../types';
import type { EventMapInfo, QueuedAction, ValidatorInfo } from './types';
import type { LeasePeriod, QueuedAction } from '../types';
import type { EventMapInfo, ValidatorInfo } from './types';
import BN from 'bn.js';
import React, { useCallback, useEffect, useMemo, useState } from 'react';

View File

@ -5,8 +5,8 @@ import type { ApiPromise } from '@polkadot/api';
import type { SignedBlockExtended } from '@polkadot/api-derive/types';
import type { AccountId, CandidateReceipt, Event, ParaId, ParaValidatorIndex } from '@polkadot/types/interfaces';
import type { IEvent } from '@polkadot/types/types';
import type { LeasePeriod, ScheduledProposals } from '../types';
import type { EventMapInfo, QueuedAction, ValidatorInfo } from './types';
import type { LeasePeriod, QueuedAction, ScheduledProposals } from '../types';
import type { EventMapInfo, ValidatorInfo } from './types';
import BN from 'bn.js';
import React, { useEffect, useMemo, useRef, useState } from 'react';
@ -15,8 +15,8 @@ import { Table } from '@polkadot/react-components';
import { useApi, useBestNumber, useCall, useCallMulti } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
import useHrmp from '../useHrmp';
import Parachain from './Parachain';
import useHrmp from './useHrmp';
interface Props {
actionsQueue: QueuedAction[];

View File

@ -1,50 +0,0 @@
// Copyright 2017-2021 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ParaId } from '@polkadot/types/interfaces';
import type { LeasePeriod } from '../types';
import type { QueuedAction } from './types';
import React, { useRef } from 'react';
import { Table } from '@polkadot/react-components';
import { useTranslation } from '../translate';
import Upcoming from './Upcoming';
interface Props {
actionsQueue: QueuedAction[];
ids?: ParaId[];
leasePeriod?: LeasePeriod;
}
function Upcomings ({ actionsQueue, ids, leasePeriod }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const headerRef = useRef([
[t('parathreads'), 'start', 3],
[t('head'), 'start'],
[t('lifecycle'), 'start'],
[t('leases')]
]);
return (
<Table
empty={leasePeriod && ids && t<string>('There are no upcoming parachains')}
header={headerRef.current}
>
{leasePeriod && ids?.map((id): React.ReactNode => (
<Upcoming
id={id}
key={id.toString()}
leasePeriod={leasePeriod}
nextAction={actionsQueue.find(({ paraIds }) =>
paraIds.some((p) => p.eq(id))
)}
/>
))}
</Table>
);
}
export default React.memo(Upcomings);

View File

@ -2,55 +2,38 @@
// SPDX-License-Identifier: Apache-2.0
import type { ParaId } from '@polkadot/types/interfaces';
import type { LeasePeriod, Proposals } from '../types';
import type { LeasePeriod, Proposals, QueuedAction } from '../types';
import React from 'react';
import { useApi, useCall } from '@polkadot/react-hooks';
import Actions from './Actions';
import Parachains from './Parachains';
import Summary from './Summary';
import Upcomings from './Upcomings';
import useActionsQueue from './useActionsQueue';
import useUpcomingIds from './useUpcomingIds';
interface Props {
actionsQueue: QueuedAction[];
className?: string;
leasePeriod?: LeasePeriod;
paraIds?: ParaId[];
proposals?: Proposals;
threadIds?: ParaId[];
upcomingIds?: ParaId[];
}
function Overview ({ className, leasePeriod, proposals }: Props): React.ReactElement<Props> {
const { api } = useApi();
const paraIds = useCall<ParaId[]>(api.query.paras?.parachains);
const actionsQueue = useActionsQueue();
const upcomingIds = useUpcomingIds();
function Overview ({ actionsQueue, className, leasePeriod, paraIds, proposals, threadIds }: Props): React.ReactElement<Props> {
return (
<div className={className}>
<Summary
leasePeriod={leasePeriod}
parachainCount={paraIds?.length}
proposalCount={proposals?.proposalIds.length}
upcomingCount={upcomingIds?.length}
upcomingCount={threadIds?.length}
/>
<Parachains
actionsQueue={actionsQueue}
ids={paraIds}
leasePeriod={leasePeriod}
scheduled={proposals?.scheduled}
/>
<Actions />
{api.query.paras && (
<>
<Parachains
actionsQueue={actionsQueue}
ids={paraIds}
leasePeriod={leasePeriod}
scheduled={proposals?.scheduled}
/>
<Upcomings
actionsQueue={actionsQueue}
ids={upcomingIds}
leasePeriod={leasePeriod}
/>
</>
)}
</div>
);
}

View File

@ -2,20 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import type BN from 'bn.js';
import type { AccountId, BalanceOf, HrmpChannel, HrmpChannelId, ParaId, ParaValidatorIndex } from '@polkadot/types/interfaces';
export type ChannelMap = Record<string, [HrmpChannelId, HrmpChannel][]>;
export interface LeaseInfo {
accountId: AccountId;
balance: BalanceOf;
period: number;
}
export interface QueuedAction {
paraIds: ParaId[];
sessionIndex: BN;
}
import type { AccountId, ParaValidatorIndex } from '@polkadot/types/interfaces';
export interface EventMapInfo {
blockHash: string;

View File

@ -1,32 +1,30 @@
// Copyright 2017-2021 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Option, Vec } from '@polkadot/types';
import type { AccountId, BalanceOf, HeadData, ParaGenesisArgs, ParaId, ParaInfo, ParaLifecycle } from '@polkadot/types/interfaces';
import type { ITuple } from '@polkadot/types/types';
import type { LeasePeriod } from '../types';
import type { LeaseInfo, QueuedAction } from './types';
import type { Option } from '@polkadot/types';
import type { AccountId, HeadData, ParaGenesisArgs, ParaId, ParaInfo, ParaLifecycle } from '@polkadot/types/interfaces';
import type { LeaseInfo, LeasePeriod, QueuedAction } from '../types';
import React, { useMemo } from 'react';
import { AddressSmall, ParaLink } from '@polkadot/react-components';
import { useApi, useCallMulti } from '@polkadot/react-hooks';
import { AddressSmall, ParaLink, TxButton } from '@polkadot/react-components';
import { useAccounts, useApi, useCallMulti } from '@polkadot/react-hooks';
import { formatNumber } from '@polkadot/util';
import Lifecycle from '../Overview/Lifecycle';
import Periods from '../Overview/Periods';
import { useTranslation } from '../translate';
import { sliceHex } from '../util';
import Lifecycle from './Lifecycle';
import Periods from './Periods';
interface Props {
id: ParaId;
leasePeriod: LeasePeriod;
leases: LeaseInfo[];
nextAction?: QueuedAction;
}
interface MultiState {
headHex: string | null;
leases: LeaseInfo[] | null;
lifecycle: ParaLifecycle | null;
manager: AccountId | null;
}
@ -34,33 +32,15 @@ interface MultiState {
const optMulti = {
defaultValue: {
headHex: null,
leases: null,
lifecycle: null,
manager: null
},
transform: ([optHead, optGenesis, optLifecycle, optInfo, leases]: [Option<HeadData>, Option<ParaGenesisArgs>, Option<ParaLifecycle>, Option<ParaInfo>, Vec<Option<ITuple<[AccountId, BalanceOf]>>>]): MultiState => ({
transform: ([optHead, optGenesis, optLifecycle, optInfo]: [Option<HeadData>, Option<ParaGenesisArgs>, Option<ParaLifecycle>, Option<ParaInfo>]): MultiState => ({
headHex: optHead.isSome
? sliceHex(optHead.unwrap())
: optGenesis.isSome
? sliceHex(optGenesis.unwrap().genesisHead)
: null,
leases: leases
? leases
.map((optLease, period): LeaseInfo | null => {
if (optLease.isNone) {
return null;
}
const [accountId, balance] = optLease.unwrap();
return {
accountId,
balance,
period
};
})
.filter((item): item is LeaseInfo => !!item)
: [],
lifecycle: optLifecycle.unwrapOr(null),
manager: optInfo.isSome
? optInfo.unwrap().manager
@ -68,15 +48,15 @@ const optMulti = {
})
};
function Upcoming ({ id, leasePeriod, nextAction }: Props): React.ReactElement<Props> {
function Upcoming ({ id, leasePeriod, leases, nextAction }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { headHex, leases, lifecycle, manager } = useCallMulti<MultiState>([
const { isAccount } = useAccounts();
const { headHex, lifecycle, manager } = useCallMulti<MultiState>([
[api.query.paras.heads, id],
[api.query.paras.upcomingParasGenesis, id],
[api.query.paras.paraLifecycles, id],
[api.query.registrar?.paras, id],
[api.query.slots?.leases, id]
[api.query.registrar?.paras, id]
], optMulti);
const periods = useMemo(
@ -85,6 +65,8 @@ function Upcoming ({ id, leasePeriod, nextAction }: Props): React.ReactElement<P
[leasePeriod?.currentPeriod, leases]
);
const isManager = isAccount(manager?.toString());
return (
<tr>
<td className='number'><h1>{formatNumber(id)}</h1></td>
@ -110,6 +92,16 @@ function Upcoming ({ id, leasePeriod, nextAction }: Props): React.ReactElement<P
: t('None')
)}
</td>
<td className='button'>
<TxButton
accountId={manager}
icon='times'
isDisabled={!isManager}
label={t<string>('Deregister')}
params={[id]}
tx={api.tx.registrar.deregister}
/>
</td>
</tr>
);
}

View File

@ -0,0 +1,100 @@
// Copyright 2017-2021 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Option } from '@polkadot/types';
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 { Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
import Actions from './Actions';
import Parathread from './Parathread';
interface Props {
actionsQueue: QueuedAction[];
className?: string;
ids?: ParaId[];
leasePeriod?: LeasePeriod;
}
type ParaMap = [ParaId, LeaseInfo[]][];
const optLeases = {
transform: ([[paraIds], leases]: [[ParaId[]], Option<ITuple<[AccountId, BalanceOf]>>[][]]): ParaMap =>
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();
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
};
function Parathreads ({ actionsQueue, className, ids, leasePeriod }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const leaseMap = useCall<ParaMap>(ids && api.query.slots.leases.multi, [ids], optLeases);
const headerRef = useRef([
[t('parathreads'), 'start', 3],
[t('head'), 'start'],
[t('lifecycle'), 'start'],
[t('leases')],
[]
]);
return (
<div className={className}>
<Actions />
<Table
empty={leasePeriod && ids && (ids.length === 0 || leaseMap) && t<string>('There are no available parathreads')}
header={headerRef.current}
>
{leasePeriod && leaseMap?.map(([id, leases]): React.ReactNode => (
<Parathread
id={id}
key={id.toString()}
leasePeriod={leasePeriod}
leases={leases}
nextAction={actionsQueue.find(({ paraIds }) =>
paraIds.some((p) => p.eq(id))
)}
/>
))}
</Table>
</div>
);
}
export default React.memo(Parathreads);

View File

@ -1,24 +1,29 @@
// Copyright 2017-2021 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ParaId } from '@polkadot/types/interfaces';
import React, { useRef } from 'react';
import { Route, Switch } from 'react-router';
import { useLocation } from 'react-router-dom';
import styled from 'styled-components';
import { Tabs } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { useApi, useCall } from '@polkadot/react-hooks';
import Auctions from './Auctions';
import Crowdloan from './Crowdloan';
import Overview from './Overview';
import Parathreads from './Parathreads';
import Proposals from './Proposals';
import { useTranslation } from './translate';
import useActionsQueue from './useActionsQueue';
import useAuctionInfo from './useAuctionInfo';
import useFunds from './useFunds';
import useLeasePeriod from './useLeasePeriod';
import useOwnedIds from './useOwnedIds';
import useProposals from './useProposals';
import useUpcomingIds from './useUpcomingIds';
import useWinningData from './useWinningData';
interface Props {
@ -36,6 +41,9 @@ function ParachainsApp ({ basePath, className }: Props): React.ReactElement<Prop
const ownedIds = useOwnedIds();
const winningData = useWinningData(auctionInfo);
const proposals = useProposals();
const paraIds = useCall<ParaId[]>(api.query.paras?.parachains);
const actionsQueue = useActionsQueue();
const upcomingIds = useUpcomingIds();
const items = useRef([
{
@ -43,6 +51,10 @@ function ParachainsApp ({ basePath, className }: Props): React.ReactElement<Prop
name: 'overview',
text: t<string>('Overview')
},
{
name: 'parathreads',
text: t<string>('Parathreads')
},
api.query.proposeParachain && {
name: 'proposals',
text: t<string>('Proposals')
@ -85,9 +97,18 @@ function ParachainsApp ({ basePath, className }: Props): React.ReactElement<Prop
</Route>
</Switch>
<Overview
className={basePath === pathname ? '' : 'parachains--hidden'}
actionsQueue={actionsQueue}
className={pathname === basePath ? '' : 'parachains--hidden'}
leasePeriod={leasePeriod}
paraIds={paraIds}
proposals={proposals}
threadIds={upcomingIds}
/>
<Parathreads
actionsQueue={actionsQueue}
className={pathname === `${basePath}/parathreads` ? '' : 'parachains--hidden'}
ids={upcomingIds}
leasePeriod={leasePeriod}
/>
</main>
);

View File

@ -2,7 +2,25 @@
// SPDX-License-Identifier: Apache-2.0
import type BN from 'bn.js';
import type { AuctionIndex, BlockNumber, FundInfo, LeasePeriodOf, ParachainProposal, ParaId, ParaInfo, SessionIndex } from '@polkadot/types/interfaces';
import type { AccountId, AuctionIndex, BalanceOf, BlockNumber, FundInfo, HrmpChannel, HrmpChannelId, LeasePeriodOf, ParachainProposal, ParaId, ParaInfo, SessionIndex } from '@polkadot/types/interfaces';
export type ChannelMap = Record<string, [HrmpChannelId, HrmpChannel][]>;
export interface AllChannels {
dst: ChannelMap;
src: ChannelMap;
}
export interface LeaseInfo {
accountId: AccountId;
balance: BalanceOf;
period: number;
}
export interface QueuedAction {
paraIds: ParaId[];
sessionIndex: BN;
}
export interface AuctionInfo {
endBlock: BlockNumber | null;

View File

@ -169,7 +169,7 @@ export default function useFunds (): Campaigns {
const [paraIds, setParaIds] = useState<ParaId[]>([]);
const trigger = useEventTrigger([api.events.crowdloan?.Created]);
const campaigns = useCall<Campaign[]>(api.query.crowdloan?.funds.multi, [paraIds], optFundMulti);
const leases = useCall<ParaId[]>(api.query.slots?.leases.multi, [paraIds], optLeaseMulti);
const leases = useCall<ParaId[]>(api.query.slots.leases.multi, [paraIds], optLeaseMulti);
const [result, setResult] = useState<Campaigns>(EMPTY);
// on event triggers, update the available paraIds

View File

@ -3,24 +3,19 @@
import type { Option } from '@polkadot/types';
import type { HrmpChannel, HrmpChannelId } from '@polkadot/types/interfaces';
import type { ChannelMap } from './types';
import type { AllChannels } from './types';
import { useEffect, useState } from 'react';
import { useApi, useCall, useEventTrigger } from '@polkadot/react-hooks';
interface Result {
dst: ChannelMap;
src: ChannelMap;
}
const optChannels = {
transform: ([[channelIds], channels]: [[HrmpChannelId[]], Option<HrmpChannel>[]]): Result =>
transform: ([[channelIds], channels]: [[HrmpChannelId[]], Option<HrmpChannel>[]]): AllChannels =>
channelIds
.map((id, index): [HrmpChannelId, Option<HrmpChannel>] => [id, channels[index]])
.filter(([, opt]) => opt.isSome)
.map(([id, opt]): [HrmpChannelId, HrmpChannel] => [id, opt.unwrap()])
.reduce((all: Result, [id, channel]): Result => {
.reduce((all: AllChannels, [id, channel]): AllChannels => {
all.dst[id.receiver.toString()] ||= [];
all.src[id.sender.toString()] ||= [];
@ -32,11 +27,11 @@ const optChannels = {
withParamsTransform: true
};
export default function useHrmp (): Result | undefined {
export default function useHrmp (): AllChannels | undefined {
const { api } = useApi();
const [channelIds, setChannelIds] = useState<HrmpChannelId[] | null>(null);
const trigger = useEventTrigger([api.events.hrmp?.OpenChannelAccepted, api.events.hrmp?.ChannelClosed]);
const allChannels = useCall<Result>(channelIds && api.query.hrmp?.hrmpChannels.multi, [channelIds], optChannels);
const allChannels = useCall<AllChannels>(channelIds && api.query.hrmp?.hrmpChannels.multi, [channelIds], optChannels);
useEffect((): void => {
trigger &&

View File

@ -13,7 +13,7 @@ export default function useLeasePeriod (): LeasePeriod | undefined {
const bestNumber = useBestNumber();
return useMemo((): LeasePeriod | undefined => {
if (!api.consts.slots?.leasePeriod || !bestNumber) {
if (!api.consts.slots.leasePeriod || !bestNumber) {
return;
}

View File

@ -10,7 +10,7 @@ import { useIsMountedRef } from './useIsMountedRef';
interface UseAccounts {
allAccounts: string[];
hasAccounts: boolean;
isAccount: (address: string) => boolean;
isAccount: (address?: string | null) => boolean;
}
const EMPTY: UseAccounts = { allAccounts: [], hasAccounts: false, isAccount: () => false };
@ -24,7 +24,7 @@ export function useAccounts (): UseAccounts {
if (mountedRef.current) {
const allAccounts = accounts ? Object.keys(accounts) : [];
const hasAccounts = allAccounts.length !== 0;
const isAccount = (address: string) => allAccounts.includes(address);
const isAccount = (address?: string | null) => !!address && allAccounts.includes(address);
setState({ allAccounts, hasAccounts, isAccount });
}