Rework explorer with events -> extrinsics (#2490)

* Rework explorer with events -> extrinsics

* Nonce adjust

* Adjust unsigned
This commit is contained in:
Jaco Greeff 2020-03-31 12:18:27 +02:00 committed by GitHub
parent fd4ae0ae51
commit 16f1d2de20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 205 additions and 189 deletions

View File

@ -7,12 +7,12 @@ import { EventRecord, SignedBlock } from '@polkadot/types/interfaces';
import React from 'react';
import { Link } from 'react-router-dom';
import { HeaderExtended } from '@polkadot/api-derive';
import { AddressMini, Columar, LinkExternal, Table } from '@polkadot/react-components';
import { AddressMini, Columar, Column, LinkExternal, Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate';
import Events from './Events';
import Events from '../Events';
import Extrinsics from './Extrinsics';
import Logs from './Logs';
@ -60,14 +60,25 @@ function BlockByHash ({ className, value }: Props): React.ReactElement<Props> {
</Table.Body>
</Table>
{getBlock && getHeader && (
<Columar>
<>
<Extrinsics
blockNumber={blockNumber}
events={events}
value={getBlock.block.extrinsics}
/>
<Events value={events} />
<Logs value={getHeader.digest.logs} />
</Columar>
<Columar>
<Column>
<Events
eventClassName='explorer--BlockByHash-block'
events={(events || []).filter(({ phase }) => !phase.isApplyExtrinsic)}
label={t('system events')}
/>
</Column>
<Column>
<Logs value={getHeader.digest.logs} />
</Column>
</Columar>
</>
)}
</div>
);

View File

@ -1,39 +0,0 @@
// 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.
import { EventRecord } from '@polkadot/types/interfaces';
import React from 'react';
import { Column } from '@polkadot/react-components';
import EventsDisplay from '../Events';
import { useTranslation } from '../translate';
interface Props {
value?: EventRecord[];
}
function Events ({ value }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
if (!value || !value.length) {
return null;
}
return (
<Column>
<EventsDisplay
eventClassName='explorer--BlockByHash-block'
events={
value.map((record, index): { key: string; record: EventRecord } => ({
key: `${index}`, record
}))
}
label={t('events')}
/>
</Column>
);
}
export default React.memo(Events);

View File

@ -0,0 +1,121 @@
// 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.
import { BlockNumber, EventRecord, Extrinsic } from '@polkadot/types/interfaces';
import React from 'react';
import styled from 'styled-components';
import { registry } from '@polkadot/react-api';
import { AddressMini, Call, Expander, LinkExternal } from '@polkadot/react-components';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate';
import Event from '../Event';
interface Props {
blockNumber?: BlockNumber;
className?: string;
events?: EventRecord[];
index: number;
value: Extrinsic;
}
function getEra ({ era }: Extrinsic, blockNumber?: BlockNumber): [number, number] | null {
if (blockNumber && era.isMortalEra) {
const mortalEra = era.asMortalEra;
return [mortalEra.birth(blockNumber.toNumber()), mortalEra.death(blockNumber.toNumber())];
}
return null;
}
function filterEvents (index: number, events: EventRecord[] = []): EventRecord[] {
return events.filter(({ phase }) => phase.isApplyExtrinsic && phase.asApplyExtrinsic.eq(index));
}
function ExtrinsicDisplay ({ blockNumber, className, events, index, value }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { meta, method, section } = registry.findMetaCall(value.callIndex);
const era = getEra(value, blockNumber);
const thisEvents = filterEvents(index, events);
return (
<tr
className={className}
key={`extrinsic:${index}`}
>
<td className='top'>
<Expander
summary={`${section}.${method} (#${formatNumber(index)})`}
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={value.tip?.toBn()}
value={value}
withHash
/>
</Expander>
{value.isSigned
? <LinkExternal data={value.hash.toHex()} type='extrinsic' />
: null
}
</td>
<td className='top'>
{value.isSigned
? (
<>
<AddressMini value={value.signer} />
<div className='explorer--BlockByHash-nonce'>
{t('index')} {formatNumber(value.nonce)}
</div>
</>
)
: <div className='explorer--BlockByHash-unsigned'>{t('not signed')}</div>
}
</td>
<td className='all top'>
{thisEvents.map((event, index) =>
<Event
className='explorer--BlockByHash-event'
key={`event:${index}`}
value={event}
/>
)}
</td>
</tr>
);
}
export default React.memo(styled(ExtrinsicDisplay)`
.explorer--BlockByHash-event+.explorer--BlockByHash-event {
margin-top: 0.75rem;
}
.explorer--BlockByHash-nonce {
font-size: 0.75rem;
margin-left: 2.25rem;
margin-top: -0.5rem;
opacity: 0.6;
text-align: left;
}
.explorer--BlockByHash-unsigned {
opacity: 0.6;
font-weight: 100;
}
`);

View File

@ -2,110 +2,44 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { BlockNumber, Extrinsic } from '@polkadot/types/interfaces';
import { BlockNumber, EventRecord, 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, Table } from '@polkadot/react-components';
import { formatNumber } from '@polkadot/util';
import { Table } from '@polkadot/react-components';
import { useTranslation } from '../translate';
import ExtrinsicDisplay from './Extrinsic';
interface Props {
blockNumber?: BlockNumber;
className?: string;
events?: EventRecord[];
label?: React.ReactNode;
value?: Extrinsic[] | null;
}
function getEra ({ era }: Extrinsic, blockNumber?: BlockNumber): [number, number] | null {
if (blockNumber && era.isMortalEra) {
const mortalEra = era.asMortalEra;
return [mortalEra.birth(blockNumber.toNumber()), mortalEra.death(blockNumber.toNumber())];
}
return null;
}
function Extrinsics ({ className, blockNumber, label, value }: Props): React.ReactElement<Props> {
function Extrinsics ({ className, blockNumber, events, label, value }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (
<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 (
<tr key={`extrinsic:${index}`}>
<td className='relative overflow'>
{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
summary={`${section}.${method} (#${formatNumber(index)})`}
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>
<Table className={className}>
<Table.Head>
<th className='start'><h1>{label || t('extrinsics')}</h1></th>
<th className='address'>{t('signer')}</th>
<th className='start'>{t('events')}</th>
</Table.Head>
<Table.Body empty={t('No pending extrinsics are in the queue')}>
{value?.map((extrinsic, index): React.ReactNode =>
<ExtrinsicDisplay
blockNumber={blockNumber}
events={events}
index={index}
value={extrinsic}
/>
)}
</Table.Body>
</Table>
);
}
export default React.memo(styled(Extrinsics)`
.explorer--BlockByHash-header {
position: absolute;
top: 0.75rem;
right: 0.75rem;
}
.explorer--BlockByHash-nonce {
font-size: 0.75rem;
margin-left: 2.25rem;
margin-top: -0.625rem;
opacity: 0.45;
text-align: left;
}
`);
export default React.memo(Extrinsics);

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, Table } from '@polkadot/react-components';
import { Expander, Table } from '@polkadot/react-components';
import Params from '@polkadot/react-params';
import { useTranslation } from '../translate';
@ -103,29 +103,23 @@ function Logs (props: Props): React.ReactElement<Props> | null {
const { value } = props;
const { t } = useTranslation();
if (!value || !value.length) {
return null;
}
return (
<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'>
<Expander summary={log.type.toString()}>
{formatItem(log)}
</Expander>
</td>
</tr>
))}
</Table.Body>
</Table>
</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'>
<Expander summary={log.type.toString()}>
{formatItem(log)}
</Expander>
</td>
</tr>
))}
</Table.Body>
</Table>
);
}

View File

@ -2,25 +2,21 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { KeyedEvent } from './types';
import { EventRecord } from '@polkadot/types/interfaces';
import React from 'react';
import { Event as EventDisplay, Expander } from '@polkadot/react-components';
import { formatNumber } from '@polkadot/util';
interface Props {
event: KeyedEvent;
withoutIndex?: boolean;
className?: string;
value: EventRecord;
}
function Event ({ event: { record: { event, phase } }, withoutIndex }: Props): React.ReactElement<Props> {
function Event ({ className, value: { event } }: Props): React.ReactElement<Props> {
return (
<Expander
summary={`${event.section}.${event.method} ${
!withoutIndex && phase.isApplyExtrinsic
? `(#${formatNumber(phase.asApplyExtrinsic)})`
: ''
}`}
className={className}
summary={`${event.section}.${event.method}`}
summaryMeta={event.meta}
>
<EventDisplay

View File

@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { KeyedEvent } from './types';
import { EventRecord } from '@polkadot/types/interfaces';
import React from 'react';
import { Table } from '@polkadot/react-components';
@ -12,13 +12,12 @@ import { useTranslation } from './translate';
interface Props {
emptyLabel?: React.ReactNode;
events: KeyedEvent[];
events: EventRecord[];
eventClassName?: string;
label?: React.ReactNode;
withoutIndex?: boolean;
}
function Events ({ emptyLabel, eventClassName, events, label, withoutIndex }: Props): React.ReactElement<Props> {
function Events ({ emptyLabel, eventClassName, events, label }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (
@ -28,17 +27,14 @@ function Events ({ emptyLabel, eventClassName, events, label, withoutIndex }: Pr
</Table.Head>
<Table.Body empty={emptyLabel || t('No events available')}>
{events
.filter(({ record: { event: { method, section } } }): boolean => !!method && !!section)
.map((event: KeyedEvent): React.ReactNode => (
.filter(({ event: { method, section } }): boolean => !!method && !!section)
.map((event: EventRecord, index): React.ReactNode => (
<tr
className={eventClassName}
key={event.key}
key={`event:${index}`}
>
<td className='overflow'>
<Event
event={event}
withoutIndex={withoutIndex}
/>
<Event value={event} />
</td>
</tr>
))

View File

@ -28,7 +28,7 @@ function Main ({ events, headers }: Props): React.ReactElement<Props> {
<BlockHeaders headers={headers} />
</Column>
<Column>
<Events events={events} />
<Events events={events.map(({ record }) => record)} />
</Column>
</Columar>
</>

View File

@ -28,15 +28,6 @@ class Column extends Collection<Props, State> {
return children;
}
// renderEmpty () {
// const { emptyText } = this.props;
// return (
// <article className='container'>
// {emptyText}
// </article>
// );
// }
}
export default translate(
@ -47,7 +38,15 @@ export default translate(
max-width: 100%;
flex: 1 1;
margin: 0;
padding: 0.5rem;
padding: 0 0.5rem;
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
}
.container {
margin: 0;

View File

@ -242,6 +242,10 @@ const Memo = React.memo(styled(Table)`
white-space: nowrap;
}
&.top {
vertical-align: top;
}
&.middle {
text-align: center;
}