Adjust all table layouts with headers (#2480)

* Some small CSS adjustments

* Adjust all table layouts with headers

* Adjust... somewhat

* Adjust

* Ok, enough for now...

* Adjust extrinsic from display

* header adjustments
This commit is contained in:
Jaco Greeff 2020-03-29 18:44:39 +02:00 committed by GitHub
parent 5b592c11bf
commit c9cf0581a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 797 additions and 1068 deletions

View File

@ -123,13 +123,13 @@ export default React.memo(styled(Apps)`
}
a.apps--SideBar-Item-NavLink-active {
background: #fafafa;
background: #f2f2f2;
border-radius: 0.28571429rem 0 0 0.28571429rem;
// border-bottom: 2px solid transparent;
color: #3f3f3f;
&:hover {
background: #fafafa;
background: #f2f2f2;
color: #3f3f3f;
margin-right: 0;
}

View File

@ -79,7 +79,7 @@ function Content ({ className }: Props): React.ReactElement<Props> {
}
export default React.memo(styled(Content)`
background: #fafafa;
background: #f2f2f2;
display: flex;
flex-direction: column;
flex-grow: 1;

View File

@ -38,7 +38,7 @@ async function queryLedger (): Promise<void> {
function Overview ({ className, onStatusChange }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { allAccounts, hasAccounts } = useAccounts();
const { allAccounts } = useAccounts();
const [isCreateOpen, toggleCreate] = useToggle();
const [isImportOpen, toggleImport] = useToggle();
const [isQrOpen, toggleQr] = useToggle();
@ -116,42 +116,36 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
</>
)}
</Button.Group>
{hasAccounts
? (
<>
<div className='filter--tags'>
<Input
autoFocus
isFull
label={t('filter by name or tags')}
onChange={setFilter}
value={filter}
/>
</div>
<Table>
<Table.Head>
<th colSpan={4}>&nbsp;</th>
<th>{t('transactions')}</th>
<th>{t('balances')}</th>
<th>{t('type')}</th>
<th colSpan={2}>&nbsp;</th>
</Table.Head>
<Table.Body>
{sortedAccounts.map(({ address, isFavorite }): React.ReactNode => (
<Account
address={address}
filter={filter}
isFavorite={isFavorite}
key={address}
toggleFavorite={toggleFavorite}
/>
))}
</Table.Body>
</Table>
</>
)
: t('no accounts yet, create or import an existing')
}
<Table>
<Table.Head filter={
<div className='filter--tags'>
<Input
autoFocus
isFull
label={t('filter by name or tags')}
onChange={setFilter}
value={filter}
/>
</div>
}>
<th className='start' colSpan={4}><h1>{t('accounts')}</h1></th>
<th>{t('transactions')}</th>
<th>{t('balances')}</th>
<th>{t('type')}</th>
<th colSpan={2}>&nbsp;</th>
</Table.Head>
<Table.Body empty={t('no accounts yet, create or import an existing')}>
{sortedAccounts.map(({ address, isFavorite }): React.ReactNode => (
<Account
address={address}
filter={filter}
isFavorite={isFavorite}
key={address}
toggleFavorite={toggleFavorite}
/>
))}
</Table.Body>
</Table>
</div>
);
}

View File

@ -19,7 +19,7 @@ const STORE_FAVS = 'accounts:favorites';
function Overview ({ className, onStatusChange }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { hasAddresses, allAddresses } = useAddresses();
const { allAddresses } = useAddresses();
const [isCreateOpen, toggleCreate] = useToggle(false);
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS);
const [sortedAddresses, setSortedAddresses] = useState<SortedAddress[]>([]);
@ -54,41 +54,35 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
onStatusChange={onStatusChange}
/>
)}
{hasAddresses
? (
<>
<div className='filter--tags'>
<Input
autoFocus
isFull
label={t('filter by name or tags')}
onChange={setFilter}
value={filter}
/>
</div>
<Table>
<Table.Head>
<th colSpan={3}>&nbsp;</th>
<th>{t('transactions')}</th>
<th>{t('balances')}</th>
<th colSpan={2}>&nbsp;</th>
</Table.Head>
<Table.Body>
{sortedAddresses.map(({ address, isFavorite }): React.ReactNode => (
<Address
address={address}
filter={filter}
isFavorite={isFavorite}
key={address}
toggleFavorite={toggleFavorite}
/>
))}
</Table.Body>
</Table>
</>
)
: t('no addresses saved yet, add any existing address')
}
<Table>
<Table.Head filter={
<div className='filter--tags'>
<Input
autoFocus
isFull
label={t('filter by name or tags')}
onChange={setFilter}
value={filter}
/>
</div>
}>
<th className='start' colSpan={3}><h1>{t('contacts')}</h1></th>
<th>{t('transactions')}</th>
<th>{t('balances')}</th>
<th colSpan={2}>&nbsp;</th>
</Table.Head>
<Table.Body empty={t('no addresses saved yet, add any existing address')}>
{sortedAddresses.map(({ address, isFavorite }): React.ReactNode => (
<Address
address={address}
filter={filter}
isFavorite={isFavorite}
key={address}
toggleFavorite={toggleFavorite}
/>
))}
</Table.Body>
</Table>
</div>
);
}

View File

@ -43,7 +43,7 @@ const Payload = styled.pre`
cursor: copy;
font-family: monospace;
border: 1px dashed #c2c2c2;
background: #fafafa;
background: #f2f2f2;
padding: 1rem;
width: 100%;
margin: 1rem 0;

View File

@ -44,32 +44,27 @@ function Proposals ({ className, motions, prime }: Props): React.ReactElement<Pr
members={members}
/>
</Button.Group>
{motions?.length
? (
<Table>
<Table.Head>
<th colSpan={2}>&nbsp;</th>
<th>{t('threshold')}</th>
<th>{t('voting end')}</th>
<th className='address'>{t('aye')}</th>
<th className='address'>{t('nay')}</th>
<th colSpan={2}>&nbsp;</th>
</Table.Head>
<Table.Body>
{motions?.map((motion: DeriveCollectiveProposal): React.ReactNode => (
<Motion
isMember={isMember}
key={motion.hash.toHex()}
members={members}
motion={motion}
prime={prime}
/>
))}
</Table.Body>
</Table>
)
: <div>{t('No council motions')}</div>
}
<Table>
<Table.Head>
<th colSpan={2}>&nbsp;</th>
<th>{t('threshold')}</th>
<th>{t('voting end')}</th>
<th className='address'>{t('aye')}</th>
<th className='address'>{t('nay')}</th>
<th colSpan={2}>&nbsp;</th>
</Table.Head>
<Table.Body empty={motions && t('No council motions')}>
{motions?.map((motion: DeriveCollectiveProposal): React.ReactNode => (
<Motion
isMember={isMember}
key={motion.hash.toHex()}
members={members}
motion={motion}
prime={prime}
/>
))}
</Table.Body>
</Table>
</div>
);
}

View File

@ -7,7 +7,7 @@ import { AccountId } from '@polkadot/types/interfaces';
import { ComponentProps } from './types';
import React from 'react';
import { Spinner, Table } from '@polkadot/react-components';
import { Table } from '@polkadot/react-components';
import { useTranslation } from '../translate';
import Candidate from './Candidate';
@ -17,59 +17,43 @@ interface Props extends ComponentProps {
className?: string;
}
function Candidates ({ allVotes = {}, className, electionsInfo }: Props): React.ReactElement<Props> {
function Candidates ({ allVotes = {}, electionsInfo }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (
<div className={className}>
<h1>{t('runners up')}</h1>
{electionsInfo
? electionsInfo.runnersUp.length
? (
<Table>
<Table.Head>
<th colSpan={2}>&nbsp;</th>
<th>{t('backing')}</th>
</Table.Head>
<Table.Body>
{electionsInfo.runnersUp.map(([accountId, balance]): React.ReactNode => (
<Candidate
address={accountId}
balance={balance}
key={accountId.toString()}
voters={allVotes[accountId.toString()]}
/>
))}
</Table.Body>
</Table>
)
: <div>{t('No runners up found')}</div>
: <Spinner />
}
<h1>{t('candidates')}</h1>
{electionsInfo
? electionsInfo.candidates.length
? (
<Table>
<Table.Head>
<th colSpan={2}>&nbsp;</th>
<th>{t('backing')}</th>
</Table.Head>
<Table.Body>
{electionsInfo.candidates.map((accountId): React.ReactNode => (
<Candidate
address={accountId}
key={accountId.toString()}
voters={allVotes[accountId.toString()]}
/>
))}
</Table.Body>
</Table>
)
: <div>{t('No candidates found')}</div>
: <Spinner />
}
</div>
<>
<Table>
<Table.Head>
<th className='start' colSpan={2}><h1>{t('runners up')}</h1></th>
<th>{t('backing')}</th>
</Table.Head>
<Table.Body empty={electionsInfo && t('No runners up found')}>
{electionsInfo?.runnersUp.map(([accountId, balance]): React.ReactNode => (
<Candidate
address={accountId}
balance={balance}
key={accountId.toString()}
voters={allVotes[accountId.toString()]}
/>
))}
</Table.Body>
</Table>
<Table>
<Table.Head>
<th className='start' colSpan={2}><h1>{t('candidates')}</h1></th>
<th>{t('backing')}</th>
</Table.Head>
<Table.Body empty={electionsInfo && t('No candidates found')}>
{electionsInfo?.candidates.map((accountId): React.ReactNode => (
<Candidate
address={accountId}
key={accountId.toString()}
voters={allVotes[accountId.toString()]}
/>
))}
</Table.Body>
</Table>
</>
);
}

View File

@ -6,7 +6,7 @@ import { AccountId } from '@polkadot/types/interfaces';
import { ComponentProps } from './types';
import React from 'react';
import { Spinner, Table } from '@polkadot/react-components';
import { Table } from '@polkadot/react-components';
import { useTranslation } from '../translate';
import Candidate from './Candidate';
@ -21,33 +21,23 @@ function Members ({ allVotes = {}, className, electionsInfo, prime }: Props): Re
const { t } = useTranslation();
return (
<div className={className}>
<h1>{t('members')}</h1>
{electionsInfo
? electionsInfo.members.length
? (
<Table>
<Table.Head>
<th colSpan={2}>&nbsp;</th>
<th>{t('backing')}</th>
</Table.Head>
<Table.Body>
{electionsInfo.members.map(([accountId, balance]): React.ReactNode => (
<Candidate
address={accountId}
balance={balance}
isPrime={prime?.eq(accountId)}
key={accountId.toString()}
voters={allVotes[accountId.toString()]}
/>
))}
</Table.Body>
</Table>
)
: <div>{t('No members found')}</div>
: <Spinner />
}
</div>
<Table className={className}>
<Table.Head>
<th className='start' colSpan={2}><h1>{t('members')}</h1></th>
<th>{t('backing')}</th>
</Table.Head>
<Table.Body empty={electionsInfo && t('No members found')}>
{electionsInfo?.members.map(([accountId, balance]): React.ReactNode => (
<Candidate
address={accountId}
balance={balance}
isPrime={prime?.eq(accountId)}
key={accountId.toString()}
voters={allVotes[accountId.toString()]}
/>
))}
</Table.Body>
</Table>
);
}

View File

@ -5,7 +5,7 @@
import { DeriveDispatch } from '@polkadot/api-derive/types';
import React from 'react';
import { Spinner, Table } from '@polkadot/react-components';
import { Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
@ -21,31 +21,21 @@ function DispatchQueue ({ className }: Props): React.ReactElement<Props> | null
const queued = useCall<DeriveDispatch[]>(api.derive.democracy.dispatchQueue as any, []);
return (
<div className={`proposalSection ${className}`}>
<h1>{t('dispatch queue')}</h1>
{queued
? queued.length
? (
<Table>
<Table.Head>
<th colSpan={2}>&nbsp;</th>
<th>{t('enact')}</th>
<th colSpan={2}>&nbsp;</th>
</Table.Head>
<Table.Body>
{queued.map((entry): React.ReactNode => (
<DispatchEntry
key={entry.index.toString()}
value={entry}
/>
))}
</Table.Body>
</Table>
)
: <div>{t('Nothing queued for execution')}</div>
: <Spinner />
}
</div>
<Table className={className}>
<Table.Head>
<th className='start' colSpan={2}><h1>{t('dispatch queue')}</h1></th>
<th>{t('enact')}</th>
<th colSpan={2}>&nbsp;</th>
</Table.Head>
<Table.Body empty={queued && t('Nothing queued for execution')}>
{queued?.map((entry): React.ReactNode => (
<DispatchEntry
key={entry.index.toString()}
value={entry}
/>
))}
</Table.Body>
</Table>
);
}

View File

@ -5,7 +5,7 @@
import { DeriveProposalExternal } from '@polkadot/api-derive/types';
import React from 'react';
import { Spinner, Table } from '@polkadot/react-components';
import { Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
@ -22,25 +22,17 @@ function Externals ({ className }: Props): React.ReactElement<Props> | null {
return (
<div className={`proposalSection ${className}`}>
<h1>{t('external')}</h1>
{external
? (
<Table>
<Table.Head>
<th>&nbsp;</th>
<th className='address'>{t('proposer')}</th>
<th>{t('locked')}</th>
<th>&nbsp;</th>
</Table.Head>
<Table.Body>
<External value={external} />
</Table.Body>
</Table>
)
: external === null
? <div>{t('No external proposal')}</div>
: <Spinner />
}
<Table>
<Table.Head>
<th className='start'><h1>{t('external')}</h1></th>
<th className='address'>{t('proposer')}</th>
<th>{t('locked')}</th>
<th>&nbsp;</th>
</Table.Head>
<Table.Body empty={external === null && t('No external proposal')}>
{external && <External value={external} />}
</Table.Body>
</Table>
</div>
);
}

View File

@ -5,7 +5,7 @@
import { DeriveProposal } from '@polkadot/api-derive/types';
import React from 'react';
import { Spinner, Table } from '@polkadot/react-components';
import { Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import ProposalDisplay from './Proposal';
@ -21,32 +21,22 @@ function Proposals ({ className }: Props): React.ReactElement<Props> {
const proposals = useCall<DeriveProposal[]>(api.derive.democracy.proposals, []);
return (
<div className={`proposalSection ${className}`}>
<h1>{t('proposals')}</h1>
{proposals
? proposals.length
? (
<Table>
<Table.Head>
<th colSpan={2}>&nbsp;</th>
<th className='address'>{t('proposer')}</th>
<th>{t('locked')}</th>
<th colSpan={3}>&nbsp;</th>
</Table.Head>
<Table.Body>
{proposals.map((proposal): React.ReactNode => (
<ProposalDisplay
key={proposal.index.toString()}
value={proposal}
/>
))}
</Table.Body>
</Table>
)
: <div>{t('No active proposals')}</div>
: <Spinner />
}
</div>
<Table className={className}>
<Table.Head>
<th className='start' colSpan={2}><h1>{t('proposals')}</h1></th>
<th className='address'>{t('proposer')}</th>
<th>{t('locked')}</th>
<th colSpan={3}>&nbsp;</th>
</Table.Head>
<Table.Body empty={proposals && t('No active proposals')}>
{proposals?.map((proposal): React.ReactNode => (
<ProposalDisplay
key={proposal.index.toString()}
value={proposal}
/>
))}
</Table.Body>
</Table>
);
}

View File

@ -5,7 +5,7 @@
import { DeriveReferendumExt } from '@polkadot/api-derive/types';
import React from 'react';
import { Spinner, Table } from '@polkadot/react-components';
import { Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import Referendum from './Referendum';
@ -21,34 +21,24 @@ function Referendums ({ className }: Props): React.ReactElement<Props> {
const referendums = useCall<DeriveReferendumExt[]>(api.derive.democracy.referendums, []);
return (
<div className={`proposalSection ${className}`}>
<h1>{t('referenda')}</h1>
{referendums
? referendums.length
? (
<Table>
<Table.Head>
<th colSpan={2}>&nbsp;</th>
<th>{t('remaining')}</th>
<th>{t('activate')}</th>
<th>{t('aye')}</th>
<th>{t('nay')}</th>
<th colSpan={3}>&nbsp;</th>
</Table.Head>
<Table.Body>
{referendums.map((referendum): React.ReactNode => (
<Referendum
key={referendum.index.toString()}
value={referendum}
/>
))}
</Table.Body>
</Table>
)
: <div>{t('No active referendums')}</div>
: <Spinner />
}
</div>
<Table className={className}>
<Table.Head>
<th className='start' colSpan={2}><h1>{t('referenda')}</h1></th>
<th>{t('remaining')}</th>
<th>{t('activate')}</th>
<th>{t('aye')}</th>
<th>{t('nay')}</th>
<th colSpan={3}>&nbsp;</th>
</Table.Head>
<Table.Body empty={referendums && t('No active referendums')}>
{referendums?.map((referendum): React.ReactNode => (
<Referendum
key={referendum.index.toString()}
value={referendum}
/>
))}
</Table.Body>
</Table>
);
}

View File

@ -6,44 +6,15 @@ import { BareProps } from '@polkadot/react-components/types';
import React from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { HeaderExtended } from '@polkadot/api-derive';
import { AddressMini, LinkExternal } from '@polkadot/react-components';
import { AddressMini } from '@polkadot/react-components';
import { formatNumber } from '@polkadot/util';
interface Props extends BareProps {
isSummary?: boolean;
value?: HeaderExtended;
withExplorer?: boolean;
withLink?: boolean;
value: HeaderExtended;
}
const renderDetails = ({ number: blockNumber, extrinsicsRoot, parentHash, stateRoot }: HeaderExtended): React.ReactNode => {
const parentHex = parentHash.toHex();
return (
<div className='contains'>
<div className='info'>
<label>parentHash</label>
<span className='hash'>{
blockNumber.unwrap().gtn(1)
? <Link to={`/explorer/query/${parentHex}`}>{parentHex}</Link>
: parentHex
}</span>
</div>
<div className='info'>
<label>extrinsicsRoot</label>
<span className='hash'>{extrinsicsRoot.toHex()}</span>
</div>
<div className='info'>
<label>stateRoot</label>
<span className='hash'>{stateRoot.toHex()}</span>
</div>
</div>
);
};
function BlockHeader ({ className, isSummary, value, withExplorer, withLink }: Props): React.ReactElement<Props> | null {
function BlockHeader ({ value }: Props): React.ReactElement<Props> | null {
if (!value) {
return null;
}
@ -52,114 +23,16 @@ function BlockHeader ({ className, isSummary, value, withExplorer, withLink }: P
const textNumber = formatNumber(value.number);
return (
<article className={`explorer--BlockHeader ${className}`}>
<div className='header-outer'>
<div className='header'>
<div className='number'>{
withLink
? <Link to={`/explorer/query/${hashHex}`}>{textNumber}</Link>
: textNumber
}&nbsp;</div>
<div className='hash'>{hashHex}</div>
<div className='author ui--media-small'>{
value.author
? <AddressMini value={value.author} />
: undefined
}</div>
</div>
</div>
{
isSummary
? undefined
: renderDetails(value)
}
{
withExplorer
? <LinkExternal data={hashHex} type='block' />
: undefined
}
</article>
<tr>
<td className='number'>
<h2><Link to={`/explorer/query/${hashHex}`}>{textNumber}</Link></h2>
</td>
<td className='all hash overflow'>{hashHex}</td>
<td className='address'>
{value.author && <AddressMini value={value.author} />}
</td>
</tr>
);
}
export default React.memo(styled(BlockHeader)`
.author {
font-size: 1rem;
text-align: right;
vertical-align: middle;
> .ui--AddressMini.padded {
padding: 0;
}
}
.header {
color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: space-between;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
> div {
display: inline-block;
font-weight: 100;
vertical-align: middle;
white-space: nowrap;
}
> .number {
font-size: 2.25rem;
}
.hash {
font-size: 1.5rem;
font-family: sans-serif;
}
}
.hash {
font-family: monospace;
overflow: hidden;
text-overflow: ellipsis;
}
.number {
align-items: center;
box-sizing: border-box;
}
.contains {
border: 0;
margin-top: 0.5rem;
text-align: center;
> .info {
margin-bottom: 0.125em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.ui--Labelled {
text-align: center;
}
> label {
display: inline-block;
}
> span,
> label {
vertical-align: middle;
}
label {
margin-right: 0.5rem;
min-width: 10rem;
text-align: right;
}
}
}
`);
export default React.memo(BlockHeader);

View File

@ -4,25 +4,34 @@
import React from 'react';
import { HeaderExtended } from '@polkadot/api-derive';
import { Table } from '@polkadot/react-components';
import BlockHeader from './BlockHeader';
import { useTranslation } from './translate';
interface Props {
headers: HeaderExtended[];
}
function BlockHeaders ({ headers }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (
<>
{headers.map((header, index): React.ReactNode => (
<BlockHeader
isSummary={!!index}
key={header.number.toString()}
value={header}
withLink={!header.number.isEmpty}
/>
))}
</>
<Table>
<Table.Head>
<th className='start' colSpan={3}><h1>{t('recent blocks')}</h1></th>
</Table.Head>
<Table.Body empty={t('No blocks available')}>
{headers
.filter((header) => !!header)
.map((header): React.ReactNode => (
<BlockHeader
key={header.number.toString()}
value={header}
/>
))}
</Table.Body>
</Table>
);
}

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/camelcase */
// Copyright 2017-2020 @polkadot/app-explorer authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
@ -6,12 +5,13 @@
import { EventRecord, SignedBlock } from '@polkadot/types/interfaces';
import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { HeaderExtended } from '@polkadot/api-derive';
import { Columar, Spinner } from '@polkadot/react-components';
import { AddressMini, Columar, LinkExternal, Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { formatNumber } from '@polkadot/util';
import BlockHeader from '../BlockHeader';
import { useTranslation } from '../translate';
import Events from './Events';
import Extrinsics from './Extrinsics';
import Logs from './Logs';
@ -22,41 +22,52 @@ interface Props {
}
function BlockByHash ({ className, value }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const events = useCall<EventRecord[]>(api.query.system.events.at as any, [value], { isSingle: true });
const getBlock = useCall<SignedBlock>(api.rpc.chain.getBlock as any, [value], { isSingle: true });
const getHeader = useCall<HeaderExtended>(api.derive.chain.getHeader as any, [value]);
if (!getBlock || getBlock.isEmpty || !getHeader || getHeader.isEmpty) {
return <Spinner />;
}
return (
<div className={className}>
<header>
<BlockHeader
className='exporer--BlockByHash-BlockHeader'
value={getHeader}
withExplorer
/>
</header>
<Columar>
<Extrinsics
blockNumber={getHeader.number.unwrap()}
value={getBlock.block.extrinsics}
/>
<Events value={events} />
<Logs value={getHeader.digest.logs} />
</Columar>
<Table isFixed>
<Table.Head>
<th className='start'><h1>{getHeader ? formatNumber(getHeader.number.unwrap()) : '-'}</h1></th>
{getHeader && (
<>
<th className='start'>{t('hash')}</th>
<th className='start'>{t('parent')}</th>
<th className='start'>{t('extrinsics')}</th>
<th className='start'>{t('state')}</th>
<th>&nbsp;</th>
</>
)}
</Table.Head>
<Table.Body>
{getBlock && !getBlock.isEmpty && getHeader && !getHeader.isEmpty && (
<tr>
<td className='address'>{getHeader.author && <AddressMini value={getHeader.author} />}</td>
<td className='hash overflow'>{getHeader.hash.toHex()}</td>
<td className='hash overflow'><Link to={`/explorer/query/${getHeader.parentHash.toHex()}`}>{getHeader.parentHash.toHex()}</Link></td>
<td className='hash overflow'>{getHeader.extrinsicsRoot.toHex()}</td>
<td className='hash overflow'>{getHeader.stateRoot.toHex()}</td>
<td><LinkExternal data={value} type='block' /></td>
</tr>
)}
</Table.Body>
</Table>
{getBlock && getHeader && (
<Columar>
<Extrinsics
blockNumber={getHeader.number.unwrap()}
value={getBlock.block.extrinsics}
/>
<Events value={events} />
<Logs value={getHeader.digest.logs} />
</Columar>
)}
</div>
);
}
export default React.memo(
styled(BlockByHash)`
.exporer--BlockByHash-BlockHeader {
border: none;
box-shadow: none;
}
`
);
export default React.memo(BlockByHash);

View File

@ -22,7 +22,7 @@ function Events ({ value }: Props): React.ReactElement<Props> | null {
}
return (
<Column headerText={t('events')}>
<Column>
<EventsDisplay
eventClassName='explorer--BlockByHash-block'
events={
@ -30,6 +30,7 @@ function Events ({ value }: Props): React.ReactElement<Props> | null {
key: `${index}`, record
}))
}
label={t('events')}
/>
</Column>
);

View File

@ -7,7 +7,7 @@ import { BlockNumber, Extrinsic } from '@polkadot/types/interfaces';
import React from 'react';
import styled from 'styled-components';
import { registry } from '@polkadot/react-api';
import { AddressMini, Call, Column, Expander, LinkExternal } from '@polkadot/react-components';
import { AddressMini, Call, Column, Expander, LinkExternal, Table } from '@polkadot/react-components';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate';
@ -19,85 +19,75 @@ interface Props {
value?: Extrinsic[] | null;
}
function renderExtrinsic (props: Props, extrinsic: Extrinsic, index: number, t: (s: string, opt?: any) => string): React.ReactNode {
const { blockNumber } = props;
const { meta, method, section } = registry.findMetaCall(extrinsic.callIndex);
const isMortal = extrinsic.era.isMortalEra;
let eraEnd;
let eraStart;
function getEra ({ era }: Extrinsic, blockNumber?: BlockNumber): [number, number] | null {
if (blockNumber && era.isMortalEra) {
const mortalEra = era.asMortalEra;
if (blockNumber && isMortal) {
const mortalEra = extrinsic.era.asMortalEra;
eraEnd = mortalEra.death(blockNumber.toNumber());
eraStart = mortalEra.birth(blockNumber.toNumber());
return [mortalEra.birth(blockNumber.toNumber()), mortalEra.death(blockNumber.toNumber())];
}
return (
<article key={`extrinsic:${index}`}>
<div className='header'>
<h3>
{section}.{method}&nbsp;(#{formatNumber(index)})
</h3>
{extrinsic.isSigned && (
<div className='explorer--BlockByHash-header'>
<div>
<AddressMini value={extrinsic.signer} />
</div>
<div className='explorer--BlockByHash-nonce'>
{t('index')} {formatNumber(extrinsic.nonce)}
</div>
</div>
)}
</div>
<Expander summaryMeta={meta}>
<Call
className='details'
mortality={
isMortal
? blockNumber
? t('mortal, valid from #{{startAt}} to #{{endsAt}}', {
replace: {
endsAt: formatNumber(eraEnd),
startAt: formatNumber(eraStart)
}
})
: t('mortal')
: t('immortal')
}
tip={extrinsic.tip?.toBn()}
value={extrinsic}
withHash
/>
</Expander>
{
extrinsic.isSigned
? <LinkExternal data={extrinsic.hash.toHex()} type='extrinsic' />
: null
}
</article>
);
return null;
}
function Extrinsics (props: Props): React.ReactElement<Props> {
function Extrinsics ({ className, blockNumber, label, value }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { className, label, value } = props;
return (
<Column
className={className}
emptyText={t('No pending extrinsics are in the queue')}
headerText={label || t('extrinsics')}
>
{value?.map((extrinsic, index): React.ReactNode => {
try {
return renderExtrinsic(props, extrinsic, index, t);
} catch (error) {
console.error(error);
<Column className={className}>
<Table>
<Table.Head>
<th className='start'><h1>{label || t('extrinsics')}</h1></th>
</Table.Head>
<Table.Body empty={t('No pending extrinsics are in the queue')}>
{value?.map((extrinsic, index): React.ReactNode => {
const { meta, method, section } = registry.findMetaCall(extrinsic.callIndex);
const era = getEra(extrinsic, blockNumber);
return t('Unable to render extrinsic');
}
})}
return (
<tr key={`extrinsic:${index}`}>
<td className='relative overflow'>
<div>{section}.{method}&nbsp;(#{formatNumber(index)})</div>
{extrinsic.isSigned && (
<div className='explorer--BlockByHash-header'>
<div>
<AddressMini value={extrinsic.signer} />
</div>
<div className='explorer--BlockByHash-nonce'>
{t('index')} {formatNumber(extrinsic.nonce)}
</div>
</div>
)}
<Expander summaryMeta={meta}>
<Call
className='details'
mortality={
era
? blockNumber
? t('mortal, valid from #{{startAt}} to #{{endsAt}}', {
replace: {
endsAt: formatNumber(era[1]),
startAt: formatNumber(era[0])
}
})
: t('mortal')
: t('immortal')
}
tip={extrinsic.tip?.toBn()}
value={extrinsic}
withHash
/>
</Expander>
{
extrinsic.isSigned
? <LinkExternal data={extrinsic.hash.toHex()} type='extrinsic' />
: null
}
</td>
</tr>
);
})}
</Table.Body>
</Table>
</Column>
);
}
@ -105,7 +95,7 @@ function Extrinsics (props: Props): React.ReactElement<Props> {
export default React.memo(styled(Extrinsics)`
.explorer--BlockByHash-header {
position: absolute;
top: 0.25rem;
top: 0.75rem;
right: 0.75rem;
}

View File

@ -7,7 +7,7 @@ import { Codec, TypeDef } from '@polkadot/types/types';
import React from 'react';
import { Struct, Tuple, Raw, Vec, getTypeDef } from '@polkadot/types';
import { Column, Expander } from '@polkadot/react-components';
import { Column, Expander, Table } from '@polkadot/react-components';
import Params from '@polkadot/react-params';
import { useTranslation } from '../translate';
@ -85,38 +85,18 @@ function formatVector (vector: Vec<any>): React.ReactNode {
);
}
function renderItem (t: (s: string, opt?: any) => string): (item: DigestItem, index: number) => React.ReactNode {
return function LogItem (item: DigestItem, index: number): React.ReactNode {
let content: React.ReactNode;
function formatItem (item: DigestItem): React.ReactNode {
if (item.value instanceof Struct) {
return formatStruct(item.value);
} else if (item.value instanceof Tuple) {
return formatTuple(item.value);
} else if (item.value instanceof Vec) {
return formatVector(item.value);
} else if (item.value instanceof Raw) {
return formatU8a(item.value);
}
if (item.value instanceof Struct) {
content = formatStruct(item.value);
} else if (item.value instanceof Tuple) {
content = formatTuple(item.value);
} else if (item.value instanceof Vec) {
content = formatVector(item.value);
} else if (item.value instanceof Raw) {
content = formatU8a(item.value);
} else {
content = <div>{item.value.toString().split(',').join(', ')}</div>;
}
return (
<div
className='explorer--BlockByHash-block'
key={index}
>
<article className='explorer--Container'>
<div className='header'>
<h3>{item.type.toString()}</h3>
</div>
<Expander summary={t('Details')}>
{content}
</Expander>
</article>
</div>
);
};
return <div>{item.value.toString().split(',').join(', ')}</div>;
}
function Logs (props: Props): React.ReactElement<Props> | null {
@ -128,8 +108,24 @@ function Logs (props: Props): React.ReactElement<Props> | null {
}
return (
<Column headerText={t('logs')}>
{value.map(renderItem(t))}
<Column>
<Table>
<Table.Head>
<th className='start'><h1>{t('logs')}</h1></th>
</Table.Head>
<Table.Body>
{value.map((log, index) => (
<tr key={`log:${index}`}>
<td className='overflow'>
<div>{log.type.toString()}</div>
<Expander summary={t('Details')}>
{formatItem(log)}
</Expander>
</td>
</tr>
))}
</Table.Body>
</Table>
</Column>
);
}

View File

@ -5,7 +5,7 @@
import { KeyedEvent } from './types';
import React from 'react';
import { Event as EventDisplay, Expander } from '@polkadot/react-components';
import { Event as EventDisplay, Expander, Table } from '@polkadot/react-components';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from './translate';
@ -14,55 +14,44 @@ interface Props {
emptyLabel?: React.ReactNode;
events: KeyedEvent[];
eventClassName?: string;
label?: React.ReactNode;
withoutIndex?: boolean;
}
function Events ({ emptyLabel, eventClassName, events, withoutIndex }: Props): React.ReactElement<Props> {
function Events ({ emptyLabel, eventClassName, events, label, withoutIndex }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
if (!events || events.length === 0) {
return (
<article>
{emptyLabel || t('no events available')}
</article>
);
}
return (
<>
{events.map(({ key, record: { event, phase } }: KeyedEvent): React.ReactNode => {
const extIndex = !withoutIndex && phase.isApplyExtrinsic
? phase.asApplyExtrinsic
: -1;
if (!event.method || !event.section) {
return null;
}
return (
<article
className={`explorer--Container ${eventClassName}`}
key={key}
>
<div className='header'>
<h3>
{event.section}.{event.method}&nbsp;{
extIndex !== -1
? `(#${formatNumber(extIndex)})`
<Table>
<Table.Head>
<th className='start'><h1>{label || t('recent events')}</h1></th>
</Table.Head>
<Table.Body empty={emptyLabel || t('No events available')}>
{events
.filter(({ record: { event: { method, section } } }): boolean => !!method && !!section)
.map(({ key, record: { event, phase } }: KeyedEvent): React.ReactNode => (
<tr
className={eventClassName}
key={key}
>
<td className='overflow'>
<div>{event.section}.{event.method}&nbsp;{
!withoutIndex && phase.isApplyExtrinsic
? `(#${formatNumber(phase.asApplyExtrinsic)})`
: ''
}
</h3>
</div>
<Expander summaryMeta={event.meta}>
<EventDisplay
className='details'
value={event}
/>
</Expander>
</article>
);
})}
</>
}</div>
<Expander summaryMeta={event.meta}>
<EventDisplay
className='details'
value={event}
/>
</Expander>
</td>
</tr>
))
}
</Table.Body>
</Table>
);
}

View File

@ -12,7 +12,6 @@ import BlockHeaders from './BlockHeaders';
import Events from './Events';
import Query from './Query';
import Summary from './Summary';
import { useTranslation } from './translate';
interface Props {
events: KeyedEvent[];
@ -20,17 +19,15 @@ interface Props {
}
function Main ({ events, headers }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (
<>
<Query />
<Summary />
<Columar>
<Column headerText={t('recent blocks')}>
<Column>
<BlockHeaders headers={headers} />
</Column>
<Column headerText={t('recent events')}>
<Column>
<Events events={events} />
</Column>
</Columar>

View File

@ -5,107 +5,38 @@
import { PeerInfo } from '@polkadot/types/interfaces';
import React from 'react';
import styled from 'styled-components';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate';
import { Table } from '@polkadot/react-components';
interface Props {
className?: string;
peers?: PeerInfo[] | null;
}
const renderPeer = (peer: PeerInfo): React.ReactNode => {
const peerId = peer.peerId.toString();
return (
<tr key={peerId}>
<td className='roles'>{peer.roles.toString().toLowerCase()}</td>
<td className='peerid ui--media-medium'>{peerId}</td>
<td className='number'>{formatNumber(peer.bestNumber)}</td>
<td className='hash'>{peer.bestHash.toHex()}</td>
</tr>
);
};
function Peers ({ className, peers }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (
<section className={`status--Peers ${className}`}>
<h1>{t('connected peers')}</h1>
{peers && peers.length
? (
<article>
<table>
<thead>
<tr>
<th className='roles'>{t('role')}</th>
<th className='peerid ui--media-medium'>{t('peer id')}</th>
<th>{t('best #')}</th>
<th className='hash'>{t('best hash')}</th>
</tr>
</thead>
<tbody>
{peers
.sort((a, b): number => b.bestNumber.cmp(a.bestNumber))
.map(renderPeer)
}
</tbody>
</table>
</article>
)
: (
<div className='ui disabled'>
{t('no peers connected')}
</div>
)
}
</section>
<Table className={className}>
<Table.Head>
<th className='start' colSpan={2}><h1>{t('connected peers')}</h1></th>
<th className='number'>{t('best #')}</th>
<th className='start'>{t('best hash')}</th>
</Table.Head>
<Table.Body empty={t('no peers connected')}>
{peers?.sort((a, b): number => b.bestNumber.cmp(a.bestNumber)).map((peer) => (
<tr key={peer.peerId.toString()}>
<td>{peer.roles.toString().toLowerCase()}</td>
<td className='hash'>{peer.peerId.toString()}</td>
<td className='number'>{formatNumber(peer.bestNumber)}</td>
<td className='hash'>{peer.bestHash.toHex()}</td>
</tr>
))}
</Table.Body>
</Table>
);
}
export default React.memo(
styled(Peers)`
table {
width: 100%;
td, th {
padding: 0.25rem 0.5rem;
text-align: left;
white-space: nowrap;
&.hash {
font-family: monospace;
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
&.number {
text-align: right;
}
&.peerid {
font-style: italic;
text-align: left;
}
&.roles {
text-align: center;
}
}
}
tbody {
tr {
width: 100%;
&:nth-child(odd) {
background-color: #f2f2f2;
}
}
}
`
);
export default React.memo(Peers);

View File

@ -67,12 +67,12 @@ function ExplorerApp ({ basePath, className }: Props): React.ReactElement<Props>
<Route path={`${basePath}/query/:value`} component={BlockInfo} />
<Route path={`${basePath}/query`} component={BlockInfo} />
<Route path={`${basePath}/node`} component={NodeInfo} />
<Route render={(): React.ReactElement<{}> => (
<Route>
<Main
events={events}
headers={lastHeaders}
/>
)} />
</Route>
</Switch>
</main>
);

View File

@ -306,7 +306,7 @@ export default React.memo(styled(Playground)`
}
.codeflask__lines {
background: #fafafa;
background: #f2f2f2;
line-height: 18px;
padding-top: 50px;
z-index: 100;

View File

@ -5,7 +5,7 @@
import { Bid } from '@polkadot/types/interfaces';
import React from 'react';
import { Spinner, Table } from '@polkadot/react-components';
import { Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
@ -21,31 +21,21 @@ function Bids ({ className }: Props): React.ReactElement<Props> {
const bids = useCall<Bid[]>(api.query.society.bids, []);
return (
<div className={`overviewSection ${className}`}>
<h1>{t('bids')}</h1>
{bids
? bids.length
? (
<Table>
<Table.Head>
<th>&nbsp;</th>
<th>{t('kind')}</th>
<th>{t('value')}</th>
</Table.Head>
<Table.Body>
{bids.map((bid): React.ReactNode => (
<BidRow
key={bid.who.toString()}
value={bid}
/>
))}
</Table.Body>
</Table>
)
: <div>{t('No bids')}</div>
: <Spinner />
}
</div>
<Table className={className}>
<Table.Head>
<th className='start'><h1>{t('bids')}</h1></th>
<th>{t('kind')}</th>
<th>{t('value')}</th>
</Table.Head>
<Table.Body empty={bids && t('No bids')}>
{bids?.map((bid): React.ReactNode => (
<BidRow
key={bid.who.toString()}
value={bid}
/>
))}
</Table.Body>
</Table>
);
}

View File

@ -6,7 +6,7 @@ import { DeriveSocietyCandidate } from '@polkadot/api-derive/types';
import { OwnMembers } from '../types';
import React from 'react';
import { Spinner, Table } from '@polkadot/react-components';
import { Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
@ -22,36 +22,26 @@ function Candidates ({ allMembers, className, isMember, ownMembers }: Props): Re
const candidates = useCall<DeriveSocietyCandidate[]>(api.derive.society.candidates, []);
return (
<div className={`overviewSection ${className}`}>
<h1>{t('candidates')}</h1>
{candidates
? candidates.length
? (
<Table>
<Table.Head>
<th>&nbsp;</th>
<th>{t('kind')}</th>
<th>{t('value')}</th>
<th colSpan={3}>{t('votes')}</th>
<th>&nbsp;</th>
</Table.Head>
<Table.Body>
{candidates.map((candidate): React.ReactNode => (
<Candidate
allMembers={allMembers}
isMember={isMember}
key={candidate.accountId.toString()}
ownMembers={ownMembers}
value={candidate}
/>
))}
</Table.Body>
</Table>
)
: <div>{t('No candidates')}</div>
: <Spinner />
}
</div>
<Table className={className}>
<Table.Head>
<th className='start'><h1>{t('candidates')}</h1></th>
<th>{t('kind')}</th>
<th>{t('value')}</th>
<th className='start'>{t('votes')}</th>
<th>&nbsp;</th>
</Table.Head>
<Table.Body empty={candidates && t('No candidates')}>
{candidates?.map((candidate): React.ReactNode => (
<Candidate
allMembers={allMembers}
isMember={isMember}
key={candidate.accountId.toString()}
ownMembers={ownMembers}
value={candidate}
/>
))}
</Table.Body>
</Table>
);
}

View File

@ -34,30 +34,27 @@ function Defender ({ className, info, isMember, ownMembers }: Props): React.Reac
}
return (
<div className={`overviewSection ${className}`}>
<h1>{t('defender')}</h1>
<Table>
<Table.Head>
<th>&nbsp;</th>
<th colSpan={3}>{t('votes')}</th>
<th>&nbsp;</th>
</Table.Head>
<Table.Body>
<tr>
<td className='address all'>
<AddressSmall value={info.defender} />
</td>
<VoteDisplay votes={votes} />
<td className='button'>
<DefenderVoting
isMember={isMember}
ownMembers={ownMembers}
/>
</td>
</tr>
</Table.Body>
</Table>
</div>
<Table className={className}>
<Table.Head>
<th className='start'><h1>{t('defender')}</h1></th>
<th className='start'>{t('votes')}</th>
<th>&nbsp;</th>
</Table.Head>
<Table.Body>
<tr>
<td className='address all'>
<AddressSmall value={info.defender} />
</td>
<VoteDisplay votes={votes} />
<td className='button'>
<DefenderVoting
isMember={isMember}
ownMembers={ownMembers}
/>
</td>
</tr>
</Table.Body>
</Table>
);
}

View File

@ -5,7 +5,7 @@
import { DeriveSociety, DeriveSocietyMember } from '@polkadot/api-derive/types';
import React, { useEffect, useState } from 'react';
import { Spinner, Table } from '@polkadot/react-components';
import { Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
@ -29,31 +29,21 @@ function Members ({ className, info }: Props): React.ReactElement<Props> {
}, [info, members]);
return (
<div className={`overviewSection ${className}`}>
<h1>{t('members')}</h1>
{info
? filtered.length
? (
<Table>
<Table.Head>
<th colSpan={3}>&nbsp;</th>
<th>{t('strikes')}</th>
</Table.Head>
<Table.Body>
{filtered.map((member): React.ReactNode => (
<Member
isHead={info?.head?.eq(member.accountId)}
key={member.accountId.toString()}
value={member}
/>
))}
</Table.Body>
</Table>
)
: <div>{t('No active members')}</div>
: <Spinner />
}
</div>
<Table className={className}>
<Table.Head>
<th colSpan={3} className='start'><h1>{t('members')}</h1></th>
<th>{t('strikes')}</th>
</Table.Head>
<Table.Body empty={info && t('No active members')}>
{filtered.map((member): React.ReactNode => (
<Member
isHead={info?.head?.eq(member.accountId)}
key={member.accountId.toString()}
value={member}
/>
))}
</Table.Body>
</Table>
);
}

View File

@ -5,15 +5,17 @@
import { VoteSplit, VoteType } from '../types';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { AddressMini, Expander } from '@polkadot/react-components';
import { useTranslation } from '../translate';
interface Props {
className?: string;
votes?: VoteType[];
}
function VoteDisplay ({ votes }: Props): React.ReactElement<Props> {
function VoteDisplay ({ className, votes }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [{ allAye, allNay, allSkeptic }, setVoteSplit] = useState<VoteSplit>({ allAye: [], allNay: [], allSkeptic: [] });
@ -26,45 +28,43 @@ function VoteDisplay ({ votes }: Props): React.ReactElement<Props> {
}, [votes]);
return (
<>
<td>
{allSkeptic.length !== 0 && (
<Expander summary={t('Skeptics ({{count}})', { replace: { count: allSkeptic.length } })}>
{allSkeptic.map(([who]): React.ReactNode =>
<AddressMini
key={who.toString()}
value={who}
/>
)}
</Expander>
)}
</td>
<td>
{allAye.length !== 0 && (
<Expander summary={t('Approvals ({{count}})', { replace: { count: allAye.length } })}>
{allAye.map(([who]): React.ReactNode =>
<AddressMini
key={who.toString()}
value={who}
/>
)}
</Expander>
)}
</td>
<td>
{allNay.length !== 0 && (
<Expander summary={t('Rejections ({{count}})', { replace: { count: allNay.length } })}>
{allNay.map(([who]): React.ReactNode =>
<AddressMini
key={who.toString()}
value={who}
/>
)}
</Expander>
)}
</td>
</>
<td className={className}>
{allSkeptic.length !== 0 && (
<Expander summary={t('Skeptics ({{count}})', { replace: { count: allSkeptic.length } })}>
{allSkeptic.map(([who]): React.ReactNode =>
<AddressMini
key={who.toString()}
value={who}
/>
)}
</Expander>
)}
{allAye.length !== 0 && (
<Expander summary={t('Approvals ({{count}})', { replace: { count: allAye.length } })}>
{allAye.map(([who]): React.ReactNode =>
<AddressMini
key={who.toString()}
value={who}
/>
)}
</Expander>
)}
{allNay.length !== 0 && (
<Expander summary={t('Rejections ({{count}})', { replace: { count: allNay.length } })}>
{allNay.map(([who]): React.ReactNode =>
<AddressMini
key={who.toString()}
value={who}
/>
)}
</Expander>
)}
</td>
);
}
export default React.memo(VoteDisplay);
export default React.memo(styled(VoteDisplay)`
.ui--Expander+ui--Expander {
margin-left: 0.5rem;
}
`);

View File

@ -77,34 +77,29 @@ function Actions ({ allRewards, allStashes, className, isVisible, next, stakingO
{isNewStakeOpen && (
<StartStaking onClose={_toggleNewStake} />
)}
{foundStashes?.length
? (
<Table>
<Table.Head>
<th colSpan={2}>&nbsp;</th>
<th className='address'>{t('controller')}</th>
<th colSpan={3}>&nbsp;</th>
</Table.Head>
<Table.Body>
{foundStashes.map(([stashId, isOwnStash]): React.ReactNode => (
<Account
activeEra={activeEra}
allStashes={allStashes}
isOwnStash={isOwnStash}
isVisible={isVisible}
key={stashId}
next={next}
onUpdateType={_onUpdateType}
rewards={allRewards && allRewards[stashId]}
stakingOverview={stakingOverview}
stashId={stashId}
/>
))}
</Table.Body>
</Table>
)
: <div>{t('No funds staked yet. Bond funds to validate or nominate a validator.')}</div>
}
<Table>
<Table.Head>
<th className='start' colSpan={2}><h1>{t('stashes')}</h1></th>
<th className='address'>{t('controller')}</th>
<th colSpan={3}>&nbsp;</th>
</Table.Head>
<Table.Body empty={t('No funds staked yet. Bond funds to validate or nominate a validator.')}>
{foundStashes?.map(([stashId, isOwnStash]): React.ReactNode => (
<Account
activeEra={activeEra}
allStashes={allStashes}
isOwnStash={isOwnStash}
isVisible={isVisible}
key={stashId}
next={next}
onUpdateType={_onUpdateType}
rewards={allRewards && allRewards[stashId]}
stakingOverview={stakingOverview}
stashId={stashId}
/>
))}
</Table.Body>
</Table>
</div>
);
}

View File

@ -7,7 +7,7 @@ import { AccountId } from '@polkadot/types/interfaces';
import { AddressDetails } from './types';
import React, { useEffect, useReducer, useState } from 'react';
import { Input, Spinner, Table } from '@polkadot/react-components';
import { Input, Table } from '@polkadot/react-components';
import { useFavorites } from '@polkadot/react-hooks';
import { STORE_FAVS_BASE } from '../constants';
@ -116,8 +116,8 @@ function CurrentList ({ authorsMap, hasQueries, isIntentions, isVisible, lastAut
);
}, [stakingOverview, validators]);
const _renderRows = (addresses: AccountExtend[], isMain: boolean): React.ReactNode =>
addresses.map(([address, isElected, isFavorite]): React.ReactNode => (
const _renderRows = (addresses?: AccountExtend[], isMain?: boolean): React.ReactNode =>
addresses?.map(([address, isElected, isFavorite]): React.ReactNode => (
<Address
address={address}
filterName={nameFilter}
@ -136,22 +136,19 @@ function CurrentList ({ authorsMap, hasQueries, isIntentions, isVisible, lastAut
/>
));
if (!stakingOverview) {
return <Spinner />;
}
return (
<div className={`${!isVisible && 'staking--hidden'}`}>
<Input
autoFocus
isFull
label={t('filter by name, address or index')}
onChange={setNameFilter}
value={nameFilter}
/>
<Table className={isIntentions ? 'staking--hidden' : ''}>
<Table.Head>
<th colSpan={3}>&nbsp;</th>
<Table.Head filter={
<Input
autoFocus
isFull
label={t('filter by name, address or index')}
onChange={setNameFilter}
value={nameFilter}
/>
}>
<th colSpan={3} className='start'><h1>{t('validators')}</h1></th>
<th>{t('other stake')}</th>
<th>{t('own stake')}</th>
<th>{t('commission')}</th>
@ -159,24 +156,20 @@ function CurrentList ({ authorsMap, hasQueries, isIntentions, isVisible, lastAut
<th>{t('last #')}</th>
<th>&nbsp;</th>
</Table.Head>
<Table.Body>
<Table.Body empty={stakingOverview && t('No active validators found')}>
{_renderRows(validators, true)}
</Table.Body>
</Table>
{isIntentions && (
waiting
? (
<Table>
<Table.Head>
<th colSpan={9}>&nbsp;</th>
</Table.Head>
<Table.Body>
{_renderRows(elected, false)}
{_renderRows(waiting, false)}
</Table.Body>
</Table>
)
: <Spinner />
<Table>
<Table.Head>
<th colSpan={9}className='start'><h1>{t('intentions')}</h1></th>
</Table.Head>
<Table.Body empty={waiting && t('No waiting validators found')}>
{_renderRows(elected, false)}
{_renderRows(waiting, false)}
</Table.Body>
</Table>
)}
</div>
);

View File

@ -10,7 +10,7 @@ import BN from 'bn.js';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import { registry } from '@polkadot/react-api';
import { Icon, InputBalance, Spinner, Table } from '@polkadot/react-components';
import { Icon, InputBalance, Table } from '@polkadot/react-components';
import { useAccounts, useApi, useCall, useDebounce, useFavorites } from '@polkadot/react-hooks';
import { createType, Option } from '@polkadot/types';
@ -211,10 +211,6 @@ function Targets ({ className }: Props): React.ReactElement<Props> {
}
}, [allAccounts, amount, electedInfo, favorites, lastReward, sortBy, sortFromMax]);
if (!sorted) {
return <Spinner />;
}
return (
<div className={className}>
<Summary
@ -223,43 +219,37 @@ function Targets ({ className }: Props): React.ReactElement<Props> {
numValidators={validators.length}
totalStaked={totalStaked}
/>
{sorted.length
? (
<>
<InputBalance
className='balanceInput'
help={t('The amount that will be used on a per-validator basis to calculate rewards for that validator.')}
isFull
label={t('amount to use for estimation')}
onChange={setAmount}
value={_amount}
<Table>
<Table.Head filter={
<InputBalance
className='balanceInput'
help={t('The amount that will be used on a per-validator basis to calculate rewards for that validator.')}
isFull
label={t('amount to use for estimation')}
onChange={setAmount}
value={_amount}
/>
}>
<th className='start' colSpan={3}><h1>{t('validators')}</h1></th>
{['rankComm', 'rankBondTotal', 'rankBondOwn', 'rankBondOther', 'rankOverall'].map((header): React.ReactNode => (
<th
className={`isClickable ${sortBy === header && 'ui--highlight--border'} number`}
key={header}
onClick={(): void => _sort(header as 'rankComm')}
>{labels[header]}<Icon name={sortBy === header ? (sortFromMax ? 'chevron down' : 'chevron up') : 'minus'} /></th>
))}
<th>&nbsp;</th>
</Table.Head>
<Table.Body empty={sorted && t('No active validators to check for rewards available')}>
{sorted?.map((info): React.ReactNode =>
<Validator
info={info}
key={info.key}
toggleFavorite={toggleFavorite}
/>
<Table>
<Table.Head>
<th colSpan={3}>&nbsp;</th>
{['rankComm', 'rankBondTotal', 'rankBondOwn', 'rankBondOther', 'rankOverall'].map((header): React.ReactNode => (
<th
className={`isClickable ${sortBy === header && 'ui--highlight--border'} number`}
key={header}
onClick={(): void => _sort(header as 'rankComm')}
>{labels[header]}<Icon name={sortBy === header ? (sortFromMax ? 'chevron down' : 'chevron up') : 'minus'} /></th>
))}
<th>&nbsp;</th>
</Table.Head>
<Table.Body>
{sorted.map((info): React.ReactNode =>
<Validator
info={info}
key={info.key}
toggleFavorite={toggleFavorite}
/>
)}
</Table.Body>
</Table>
</>
)
: <Spinner />
}
)}
</Table.Body>
</Table>
</div>
);
}

View File

@ -11,7 +11,7 @@ import { useTranslation } from '../translate';
interface Props {
className?: string;
members: string[];
members?: string[];
prime?: AccountId | null;
}
@ -19,37 +19,30 @@ function Members ({ className, members, prime }: Props): React.ReactElement<Prop
const { t } = useTranslation();
return (
<div className={className}>
{members.length
? (
<Table>
<Table.Head>
<th colSpan={3}>&nbsp;</th>
</Table.Head>
<Table.Body>
{members.map((accountId): React.ReactNode => (
<tr key={accountId.toString()}>
<td className='address'>
<AddressSmall value={accountId} />
</td>
<td>
{prime?.eq(accountId) && (
<Tag
color='green'
hover={t('Committee prime member, default voting')}
label={t('prime member')}
/>
)}
</td>
<td className='all'>&nbsp;</td>
</tr>
))}
</Table.Body>
</Table>
)
: <div>{t('No members found')}</div>
}
</div>
<Table className={className}>
<Table.Head>
<th className='start' colSpan={3}><h1>{t('members')}</h1></th>
</Table.Head>
<Table.Body empty={members && t('No members found')}>
{members?.map((accountId): React.ReactNode => (
<tr key={accountId.toString()}>
<td className='address'>
<AddressSmall value={accountId} />
</td>
<td>
{prime?.eq(accountId) && (
<Tag
color='green'
hover={t('Committee prime member, default voting')}
label={t('prime member')}
/>
)}
</td>
<td className='all'>&nbsp;</td>
</tr>
))}
</Table.Body>
</Table>
);
}

View File

@ -6,7 +6,7 @@ import { Hash } from '@polkadot/types/interfaces';
import { ComponentProps as Props } from '../types';
import React from 'react';
import { Button, Spinner, Table } from '@polkadot/react-components';
import { Button, Table } from '@polkadot/react-components';
import { useTranslation } from '../translate';
import Proposal from './Proposal';
@ -23,31 +23,24 @@ function Proposals ({ className, isMember, members, prime, proposals }: Props):
members={members}
/>
</Button.Group>
{proposals
? proposals.length
? (
<Table>
<Table.Head>
<th colSpan={2}>&nbsp;</th>
<th>{t('threshold')}</th>
<th className='address'>{t('aye')}</th>
<th className='address'>{t('nay')}</th>
<th>&nbsp;</th>
</Table.Head>
<Table.Body>
{proposals.map((hash: Hash): React.ReactNode => (
<Proposal
imageHash={hash.toHex()}
key={hash.toHex()}
prime={prime}
/>
))}
</Table.Body>
</Table>
)
: <div>{t('No committee proposals')}</div>
: <Spinner />
}
<Table>
<Table.Head>
<th className='start' colSpan={2}><h1>{t('proposals')}</h1></th>
<th>{t('threshold')}</th>
<th className='address'>{t('aye')}</th>
<th className='address'>{t('nay')}</th>
<th>&nbsp;</th>
</Table.Head>
<Table.Body empty={proposals && t('No committee proposals')}>
{proposals?.map((hash: Hash): React.ReactNode => (
<Proposal
imageHash={hash.toHex()}
key={hash.toHex()}
prime={prime}
/>
))}
</Table.Body>
</Table>
</div>
);
}

View File

@ -7,7 +7,7 @@ import { AccountId, Balance } from '@polkadot/types/interfaces';
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { Spinner, Table } from '@polkadot/react-components';
import { Table } from '@polkadot/react-components';
import { useApi, useAccounts, useCall } from '@polkadot/react-hooks';
import Proposal from './Proposal';
@ -40,38 +40,26 @@ function ProposalsBase ({ className, isApprovals, proposals }: Props): React.Rea
};
return (
<div className={className}>
<h1>{isApprovals ? t('Approved') : t('Proposals')}</h1>
{proposals
? proposals.length
? (
<Table>
<Table.Head>
<th colSpan={2}>&nbsp;</th>
<th className='address'>{t('beneficiary')}</th>
<th>{t('payment')}</th>
<th>{t('bond')}</th>
<th colSpan={2}>&nbsp;</th>
</Table.Head>
<Table.Body>
{proposals.map((proposal): React.ReactNode => (
<Proposal
isMember={isMember}
onRespond={_onRespond}
proposal={proposal}
key={proposal.id.toString()}
withSend={!isApprovals}
/>
))}
</Table.Body>
</Table>
)
: isApprovals
? <div>{t('No approved proposals')}</div>
: <div>{t('No pending proposals')}</div>
: <Spinner />
}
</div>
<Table className={className}>
<Table.Head>
<th className='start' colSpan={2}><h1>{isApprovals ? t('Approved') : t('Proposals')}</h1></th>
<th className='address'>{t('beneficiary')}</th>
<th>{t('payment')}</th>
<th>{t('bond')}</th>
<th colSpan={2}>&nbsp;</th>
</Table.Head>
<Table.Body empty={proposals && (isApprovals ? t('No approved proposals') : t('No pending proposals'))}>
{proposals?.map((proposal): React.ReactNode => (
<Proposal
isMember={isMember}
onRespond={_onRespond}
proposal={proposal}
key={proposal.id.toString()}
withSend={!isApprovals}
/>
))}
</Table.Body>
</Table>
);
}

View File

@ -100,7 +100,7 @@ export default React.memo(styled(AddressMini)`
&.padded {
display: inline-block;
padding: 0.25rem 1rem 0 0;
padding: 0 1rem 0 0;
}
&.summary {

View File

@ -43,6 +43,10 @@ const Memo = React.memo(styled(ButtonGroup)`
margin-bottom: 0.5rem;
text-align: center;
}
&+.ui--Table {
margin-top: 1.5rem;
}
`) as unknown as GroupType;
Memo.Divider = Divider;

View File

@ -28,7 +28,7 @@ class CardGrid extends Collection<Props, State> {
return (
<div className='ui--CardGrid-empty'>
<h2>{emptyText || t('No items')}</h2>
<h1>{emptyText || t('No items')}</h1>
{buttons && (
<div className='ui--CardGrid-buttons'>
{buttons}

View File

@ -104,10 +104,10 @@ export default React.memo(styled(CardSummary)`
}
> div {
font-size: 1.85rem;
font-size: 1.75rem;
font-weight: 100;
position: relative;
line-height: 1.85rem;
line-height: 1.75rem;
text-align: right;
> * {

View File

@ -4,26 +4,40 @@
import React from 'react';
import styled from 'styled-components';
import { isString } from '@polkadot/util';
import Spinner from './Spinner';
interface BaseProps {
children: React.ReactNode;
className?: string;
}
type BodyProps = BaseProps;
interface BodyProps extends BaseProps {
empty?: React.ReactNode;
}
type HeadProps = BaseProps;
interface HeadProps extends BaseProps {
filter?: React.ReactNode;
}
type TableProps = BaseProps;
interface TableProps extends BaseProps {
isFixed?: boolean;
}
type TableImpl = React.FC<TableProps> & {
Body: React.FC<BodyProps>;
Head: React.FC<HeadProps>;
}
function Head ({ children, className }: HeadProps): React.ReactElement<HeadProps> {
function Head ({ children, className, filter }: HeadProps): React.ReactElement<HeadProps> {
return (
<thead className={className}>
{filter && (
<tr className='filter'>
<th colSpan={100}>{filter}</th>
</tr>
)}
<tr>
{children}
</tr>
@ -31,17 +45,28 @@ function Head ({ children, className }: HeadProps): React.ReactElement<HeadProps
);
}
function Body ({ children, className }: BodyProps): React.ReactElement<BodyProps> {
function Body ({ children, className, empty }: BodyProps): React.ReactElement<BodyProps> {
const isEmpty = !children || (Array.isArray(children) && children.length === 0);
return (
<tbody className={className}>
{children}
{isEmpty
? (
<tr><td colSpan={100}>{
isString(empty)
? <div className='empty'>{empty}</div>
: empty || <Spinner />
}</td></tr>
)
: children
}
</tbody>
);
}
function Table ({ children, className }: TableProps): React.ReactElement<TableProps> {
function Table ({ children, className, isFixed }: TableProps): React.ReactElement<TableProps> {
return (
<div className={`ui--Table ${className}`}>
<div className={`ui--Table ${isFixed && 'isFixed'} ${className}`}>
<table>
{children}
</table>
@ -51,21 +76,33 @@ function Table ({ children, className }: TableProps): React.ReactElement<TablePr
const Memo = React.memo(styled(Table)`
margin-bottom: 1.5rem;
margin-top: 0.25rem;
&.isFixed table {
table-layout: fixed;
}
table {
border: 1px solid #f2f2f2;
border: 1px solid #eee;
border-radius: 0.25rem;
border-spacing: 0;
overflow: hidden;
width: 100%;
thead tr {
background: #f5f5f5;
background: #f9f9f9;
text-transform: lowercase;
&.filter th {
padding: 0.25rem 0.5rem 0;
}
}
tbody tr {
background: white;
td {
border-top: 1px solid #f2f2f2;
}
}
tr {
@ -104,8 +141,13 @@ const Memo = React.memo(styled(Table)`
}
td, th {
padding: 0.5rem 0.75rem;
vertical-align: middle;
&:first-child {
padding-left: 1.5rem;
}
&:last-child {
padding-right: 1.5rem;
}
&.all {
width: 100%;
@ -117,8 +159,10 @@ const Memo = React.memo(styled(Table)`
}
td {
border-top: 1px solid #f2f2f2;
// border-top: 1px solid #f2f2f2;
padding: 0.75rem 1rem;
text-align: left;
vertical-align: middle;
label {
display: block !important;
@ -129,6 +173,19 @@ const Memo = React.memo(styled(Table)`
cursor: pointer;
}
div.empty {
opacity: 0.6;
padding: 0.25rem;
}
.ui--Spinner {
margin: auto 0.5rem;
.text {
margin-bottom: 0;
}
}
&.address {
min-width: 11rem;
padding: 0.85rem 1rem;
@ -147,14 +204,40 @@ const Memo = React.memo(styled(Table)`
white-space: nowrap;
}
&.combined {
border-top-width: 0;
}
&.empty {
opacity: 0.6;
}
&.hash {
font-family: monospace;
}
&.number {
text-align: right;
}
&.relative {
position: relative;
}
&.overflow {
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
&.together {
white-space: nowrap;
}
&.middle {
text-align: center;
}
&.mini {
padding: 0 0.75rem 0 0;
white-space: nowrap;
@ -166,12 +249,19 @@ const Memo = React.memo(styled(Table)`
}
th {
color: #888;
color: rgba(0, 0, 0, 0.6);
font-family: sans-serif;
font-weight: 400;
text-align: right;
padding: 0.75rem 1rem 0.25rem;
vertical-align: baseline;
white-space: nowrap;
h1, h2 {
color: inherit;
font-size: 1.75rem;
}
&:first-child {
border-top-left-radius: 0.25rem;
}

View File

@ -99,7 +99,7 @@ export default css`
}
.ui.modal {
background: #fafafa;
background: #f9f9f9;
color: #4e4e4e;
font-family: sans-serif;