Enable Forks, swap Explorer app to func (#1637)
* Enable Forks, swap Explorer app to func * thisEventHash -> newEventHash * Linting * Back to fragments
This commit is contained in:
parent
11b11e3a5b
commit
51fcab9fc8
@ -2,61 +2,26 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { CallProps } from '@polkadot/react-api/types';
|
||||
|
||||
import React from 'react';
|
||||
import { HeaderExtended } from '@polkadot/api-derive';
|
||||
import { withCalls } from '@polkadot/react-api';
|
||||
|
||||
import BlockHeader from './BlockHeader';
|
||||
|
||||
export const MAX_ITEMS = 15;
|
||||
|
||||
let blockHeaders: HeaderExtended[] = [];
|
||||
|
||||
const transform = (header: HeaderExtended): HeaderExtended[] => {
|
||||
if (!header) {
|
||||
return blockHeaders;
|
||||
}
|
||||
|
||||
blockHeaders = blockHeaders
|
||||
.filter((old, index): boolean =>
|
||||
index < MAX_ITEMS && old.number.unwrap().lt(header.number.unwrap())
|
||||
)
|
||||
.reduce((next, header): HeaderExtended[] => {
|
||||
next.push(header);
|
||||
|
||||
return next;
|
||||
}, [header])
|
||||
.sort((a, b): number =>
|
||||
b.number.unwrap().cmp(a.number.unwrap())
|
||||
);
|
||||
|
||||
return blockHeaders;
|
||||
};
|
||||
|
||||
interface Props extends CallProps {
|
||||
headers?: HeaderExtended[];
|
||||
interface Props {
|
||||
headers: HeaderExtended[];
|
||||
}
|
||||
|
||||
function BlockHeaders ({ headers = [] }: Props): React.ReactElement<Props> {
|
||||
export default function BlockHeaders ({ headers }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
headers.map((header, index): React.ReactNode => (
|
||||
<BlockHeader
|
||||
isSummary={!!index}
|
||||
key={header.number.toString()}
|
||||
value={header}
|
||||
withLink={!header.number.isEmpty}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{headers.map((header, index): React.ReactNode => (
|
||||
<BlockHeader
|
||||
isSummary={!!index}
|
||||
key={header.number.toString()}
|
||||
value={header}
|
||||
withLink={!header.number.isEmpty}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default withCalls<Props>(['derive.chain.subscribeNewHeads', {
|
||||
propName: 'headers',
|
||||
transform
|
||||
}])(BlockHeaders);
|
||||
|
||||
@ -29,49 +29,47 @@ function Events ({ emptyLabel, eventClassName, events, withoutIndex, t }: Props)
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
events.map(
|
||||
({ key, record: { event, phase } }: KeyedEvent): React.ReactNode => {
|
||||
const extIndex = !withoutIndex && phase.isApplyExtrinsic
|
||||
? phase.asApplyExtrinsic
|
||||
: -1;
|
||||
{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} {
|
||||
extIndex !== -1
|
||||
? `(#${formatNumber(extIndex)})`
|
||||
: ''
|
||||
}
|
||||
</h3>
|
||||
</div>
|
||||
<details>
|
||||
<summary>
|
||||
{
|
||||
event.meta && event.meta.documentation
|
||||
? event.meta.documentation.join(' ')
|
||||
: 'Details'
|
||||
}
|
||||
</summary>
|
||||
<EventDisplay
|
||||
className='details'
|
||||
value={event}
|
||||
/>
|
||||
</details>
|
||||
</article>
|
||||
);
|
||||
if (!event.method || !event.section) {
|
||||
return null;
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<article
|
||||
className={`explorer--Container ${eventClassName}`}
|
||||
key={key}
|
||||
>
|
||||
<div className='header'>
|
||||
<h3>
|
||||
{event.section}.{event.method} {
|
||||
extIndex !== -1
|
||||
? `(#${formatNumber(extIndex)})`
|
||||
: ''
|
||||
}
|
||||
</h3>
|
||||
</div>
|
||||
<details>
|
||||
<summary>
|
||||
{
|
||||
event.meta && event.meta.documentation
|
||||
? event.meta.documentation.join(' ')
|
||||
: 'Details'
|
||||
}
|
||||
</summary>
|
||||
<EventDisplay
|
||||
className='details'
|
||||
value={event}
|
||||
/>
|
||||
</details>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { KeyedEvent } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import { HeaderExtended } from '@polkadot/api-derive';
|
||||
import { Columar, Column } from '@polkadot/react-components';
|
||||
|
||||
import BlockHeaders from './BlockHeaders';
|
||||
@ -16,16 +17,17 @@ import translate from './translate';
|
||||
|
||||
interface Props extends I18nProps {
|
||||
events: KeyedEvent[];
|
||||
headers: HeaderExtended[];
|
||||
}
|
||||
|
||||
function Main ({ events, t }: Props): React.ReactElement<Props> {
|
||||
function Main ({ events, headers, t }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<>
|
||||
<Query />
|
||||
<Summary />
|
||||
<Columar>
|
||||
<Column headerText={t('recent blocks')}>
|
||||
<BlockHeaders />
|
||||
<BlockHeaders headers={headers} />
|
||||
</Column>
|
||||
<Column headerText={t('recent events')}>
|
||||
<Events events={events} />
|
||||
|
||||
@ -1,132 +1,137 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
// Copyright 2017-2019 @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 { ApiProps } from '@polkadot/react-api/types';
|
||||
import { AppProps, BareProps, I18nProps } from '@polkadot/react-components/types';
|
||||
import { EventRecord } from '@polkadot/types/interfaces';
|
||||
import { KeyedEvent } from './types';
|
||||
|
||||
import './index.css';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Route, Switch } from 'react-router';
|
||||
import styled from 'styled-components';
|
||||
import { withCalls, withMulti } from '@polkadot/react-api';
|
||||
import Tabs, { TabItem } from '@polkadot/react-components/Tabs';
|
||||
import { HeaderExtended } from '@polkadot/api-derive';
|
||||
import { ApiContext, withCalls, withMulti } from '@polkadot/react-api';
|
||||
import Tabs from '@polkadot/react-components/Tabs';
|
||||
import uiSettings from '@polkadot/ui-settings';
|
||||
import { stringToU8a } from '@polkadot/util';
|
||||
import { xxhashAsHex } from '@polkadot/util-crypto';
|
||||
|
||||
import { MAX_ITEMS } from './BlockHeaders';
|
||||
import BlockInfo from './BlockInfo';
|
||||
import Forks from './Forks';
|
||||
import Main from './Main';
|
||||
import NodeInfo from './NodeInfo';
|
||||
import translate from './translate';
|
||||
|
||||
interface Props extends AppProps, BareProps, I18nProps {
|
||||
system_events?: EventRecord[];
|
||||
interface Props extends ApiProps, AppProps, BareProps, I18nProps {
|
||||
newHeader?: HeaderExtended;
|
||||
newEvents?: KeyedEvent[];
|
||||
}
|
||||
|
||||
interface State {
|
||||
items: TabItem[];
|
||||
prevEventHash: string;
|
||||
recentEvents: KeyedEvent[];
|
||||
}
|
||||
const MAX_ITEMS = 15;
|
||||
|
||||
class ExplorerApp extends React.Component<Props, State> {
|
||||
public constructor (props: Props) {
|
||||
super(props);
|
||||
function ExplorerApp ({ basePath, className, newEvents, newHeader, t }: Props): React.ReactElement<Props> {
|
||||
const [headers, setHeaders] = useState<HeaderExtended[]>([]);
|
||||
const [{ prevEventHash, events }, setEvents] = useState<{ prevEventHash: string; events: KeyedEvent[] }>({ prevEventHash: '', events: [] });
|
||||
const { api } = useContext(ApiContext);
|
||||
|
||||
const { t } = this.props;
|
||||
useEffect((): void => {
|
||||
const newEventHash = xxhashAsHex(stringToU8a(JSON.stringify(newEvents)));
|
||||
|
||||
this.state = {
|
||||
items: [
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'chain',
|
||||
text: t('Chain info')
|
||||
},
|
||||
{
|
||||
hasParams: true,
|
||||
name: 'query',
|
||||
text: t('Block details')
|
||||
},
|
||||
{
|
||||
name: 'node',
|
||||
text: t('Node info')
|
||||
}
|
||||
],
|
||||
prevEventHash: '',
|
||||
recentEvents: []
|
||||
};
|
||||
}
|
||||
|
||||
// assign the events on the index - this way we keep a record of these, even when swapping
|
||||
// tabs withing this app, all the events as received will be shown
|
||||
public static getDerivedStateFromProps ({ system_events = [] }: Props, prevState: State): State | null {
|
||||
const prevEventHash = xxhashAsHex(stringToU8a(JSON.stringify(system_events)));
|
||||
|
||||
if (prevEventHash === prevState.prevEventHash) {
|
||||
return null;
|
||||
if (newEventHash === prevEventHash || !newEvents) {
|
||||
return;
|
||||
}
|
||||
|
||||
const recentEvents = system_events
|
||||
.filter(({ event }): boolean => event.section !== 'system')
|
||||
.map((record, index): { key: string; record: EventRecord } => ({ key: `${Date.now()}-${index}`, record }))
|
||||
.concat(prevState.recentEvents)
|
||||
.filter((_, index): boolean => index < MAX_ITEMS);
|
||||
setEvents({
|
||||
prevEventHash: newEventHash,
|
||||
events: newEvents.concat(events).filter((_, index): boolean => index < MAX_ITEMS)
|
||||
});
|
||||
}, [newEvents]);
|
||||
|
||||
return {
|
||||
items: prevState.items,
|
||||
prevEventHash,
|
||||
recentEvents
|
||||
};
|
||||
}
|
||||
useEffect((): void => {
|
||||
if (!newHeader) {
|
||||
return;
|
||||
}
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { basePath, className } = this.props;
|
||||
const { items } = this.state;
|
||||
const hidden = uiSettings.uiMode === 'full'
|
||||
? []
|
||||
: ['node'];
|
||||
setHeaders(
|
||||
headers
|
||||
.filter((old, index): boolean => index < MAX_ITEMS && old.number.unwrap().lt(newHeader.number.unwrap()))
|
||||
.reduce((next, header): HeaderExtended[] => {
|
||||
next.push(header);
|
||||
|
||||
return (
|
||||
<main className={className}>
|
||||
<header>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
hidden={hidden}
|
||||
items={items}
|
||||
/>
|
||||
</header>
|
||||
<Switch>
|
||||
<Route path={`${basePath}/forks`} component={Forks} />
|
||||
<Route path={`${basePath}/query/:value`} component={BlockInfo} />
|
||||
<Route path={`${basePath}/query`} component={BlockInfo} />
|
||||
<Route path={`${basePath}/node`} component={NodeInfo} />
|
||||
<Route component={this.renderMain} />
|
||||
</Switch>
|
||||
</main>
|
||||
return next;
|
||||
}, [newHeader])
|
||||
.sort((a, b): number => b.number.unwrap().cmp(a.number.unwrap()))
|
||||
);
|
||||
}
|
||||
}, [newHeader]);
|
||||
|
||||
private renderMain = (): JSX.Element => {
|
||||
const { recentEvents } = this.state;
|
||||
|
||||
return (
|
||||
<Main events={recentEvents} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<main className={className}>
|
||||
<header>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
hidden={
|
||||
uiSettings.uiMode === 'full'
|
||||
? api.query.babe ? [] : ['forks']
|
||||
: ['node', 'forks']
|
||||
}
|
||||
items={[
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'chain',
|
||||
text: t('Chain info')
|
||||
},
|
||||
{
|
||||
hasParams: true,
|
||||
name: 'query',
|
||||
text: t('Block details')
|
||||
},
|
||||
{
|
||||
name: 'forks',
|
||||
text: t('Forks')
|
||||
},
|
||||
{
|
||||
name: 'node',
|
||||
text: t('Node info')
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</header>
|
||||
<Switch>
|
||||
<Route path={`${basePath}/forks`} component={Forks} />
|
||||
<Route path={`${basePath}/query/:value`} component={BlockInfo} />
|
||||
<Route path={`${basePath}/query`} component={BlockInfo} />
|
||||
<Route path={`${basePath}/node`} component={NodeInfo} />
|
||||
<Route
|
||||
render={(): React.ReactElement<{}> =>
|
||||
<Main
|
||||
events={events}
|
||||
headers={headers}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Switch>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
styled(ExplorerApp as React.ComponentClass<Props>)`
|
||||
styled(ExplorerApp)`
|
||||
.rx--updated {
|
||||
background: transparent !important;
|
||||
}
|
||||
`,
|
||||
translate,
|
||||
withCalls<Props>('query.system.events')
|
||||
withCalls<Props>(
|
||||
['query.system.events', {
|
||||
propName: 'newEvents',
|
||||
transform: (records: EventRecord[]): KeyedEvent[] =>
|
||||
records
|
||||
.filter(({ event }): boolean => event.section !== 'system')
|
||||
.map((record, index): KeyedEvent => ({ key: `${Date.now()}-${index}`, record }))
|
||||
}],
|
||||
['derive.chain.subscribeNewHeads', { propName: 'newHeader' }]
|
||||
)
|
||||
);
|
||||
|
||||
@ -37,7 +37,7 @@ export function TimePeriod ({ babe_expectedBlockTime, children, className, label
|
||||
// NOTE we are not combining this into one with a fallback, it becomes slightly tricky.
|
||||
// There must be a cleaner way with this logic, but as of now it works on Alex & Kusama
|
||||
// - babe bas the exact value in miliseconds
|
||||
// - non-babe on 2.x has the value in miliseconds (not catered for here)
|
||||
// - non-babe on 2.x has the value in miliseconds (not catered for here)
|
||||
// - anything non-babe, i.e. Aura, needs to be multiplied by 2
|
||||
export default withCalls<Props>(
|
||||
// substrate 2.x: consts.babe.expectedBlockTime
|
||||
|
||||
Loading…
Reference in New Issue
Block a user