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:
Jaco Greeff 2019-09-19 15:30:51 +02:00 committed by GitHub
parent 11b11e3a5b
commit 51fcab9fc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 149 additions and 179 deletions

View File

@ -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);

View File

@ -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}&nbsp;{
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}&nbsp;{
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>
);
}
)}
</>
);
}

View File

@ -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} />

View File

@ -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' }]
)
);

View File

@ -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