Restructure the js app (#243)

* prettier

* linter

* add prettier, and format the code

* remove common, merge it with frontend

* refactor the app

* better lint and code fix

* travis for the frontend app

* travis build script

Signed-off-by: Daniel Maricic <daniel@woss.io>

* lint and build

* update the README.md

Signed-off-by: Daniel Maricic <daniel@woss.io>

* change the commands to reflect refactor

Signed-off-by: Daniel Maricic <daniel@woss.io>

* prettier and tslint are friends

Signed-off-by: Daniel Maricic <daniel@woss.io>

* code that wasn't linted properly before

Signed-off-by: Daniel Maricic <daniel@woss.io>

* prettier rc got deleted

* workgin on making the travis pass

Signed-off-by: Daniel Maricic <daniel@woss.io>

* travis build please?

Signed-off-by: Daniel Maricic <daniel@woss.io>

* update readme.md

Signed-off-by: Daniel Maricic <daniel@woss.io>

* dockerfile deleted from fronted - out of scope

Signed-off-by: Daniel Maricic <daniel@woss.io>

* remove

Signed-off-by: Daniel Maricic <daniel@woss.io>

* tsconfig

Signed-off-by: Daniel Maricic <daniel@woss.io>

* found the reason why EOL wasn't happening

Signed-off-by: Daniel Maricic <daniel@woss.io>

* type for the event in the ConnectionInput

as suggested

* strictnullCheck to true

* noImplicitAny

* noUnusedParams

* AfgHandling

* update

* fix Location.tsx

* Few minor fixes

* remove connection input and revert to original

* esnext fixes the imports for icons and non default `* as `

* update to the tsconfig.test.json don't use commonjs please

* fixed wrong comment for TIMEOUT_BASE

* return totem.svg and type decraration of maybe

Signed-off-by: Daniel Maricic <daniel@woss.io>

Co-authored-by: Will <w.kopp@kigroup.de>
This commit is contained in:
Daniel Maricic
2020-04-06 15:38:45 +02:00
committed by GitHub
parent 20a0283380
commit bb8e804567
322 changed files with 10896 additions and 10602 deletions
@@ -0,0 +1,167 @@
.Consensus .ConsensusList {
opacity: 0; /* the box should only show up once flexing has been applied */
}
.Consensus .ConsensusList table {
border-spacing: 0px;
}
.Consensus .flexContainerLargeRow {
display: flex;
align-items: stretch;
flex-direction: row;
opacity: 1;
}
.Consensus .flexContainerLargeRow .firstInRow {
width: 100%;
}
.Consensus .flexContainerLargeRow .firstInRow .emptylegend,
.Consensus .flexContainerLargeRow .firstInRow .nameLegend {
width: 99%;
flex-grow: 1000000000;
align-self: stretch;
}
.Consensus .flexContainerSmallRow {
display: flex;
align-items: stretch;
flex-direction: row;
flex-wrap: wrap;
opacity: 1;
}
.Consensus .flexContainerSmallRow div {
align-self: stretch;
flex: 1;
}
.Consensus .flexContainerSmallRow table .legend {
width: 100%;
}
.Consensus .ConsensusList {
margin-bottom: 2px;
}
.Consensus {
width: 100%;
min-width: 1350px;
min-height: 100%;
position: absolute;
top: 0px;
left: 0px;
}
.Consensus .SmallRow {
float: left;
clear: both;
font-size: 8px !important;
width: 100%;
}
.Consensus .SmallRow svg {
width: 14px;
height: 14px;
}
.Consensus .hatching svg {
width: 12px !important;
height: 12px !important;
}
.Consensus .SmallRow .hatching svg {
width: 10px !important;
height: 10px !important;
}
.Consensus .matrixXLegend .Tooltip-container {
height: auto !important;
}
.Consensus .legend {
text-align: center !important;
}
.Consensus .nameLegend {
border-right: none;
border-bottom: 1px dotted #555;
}
.Consensus .SmallRow .nameLegend {
display: none;
}
.Consensus .SmallRow .finalizedInfo .Tooltip-container {
float: none;
display: inline-block !important;
vertical-align: middle;
}
.Consensus .SmallRow .finalizedInfo {
min-height: 40px;
min-width: 40px;
}
.Consensus .SmallRow .explicit,
.Consensus .SmallRow .implicit {
height: 12px;
}
.Consensus .SmallRow .finalizedInfo .explicit,
.Consensus .SmallRow .finalizedInfo .implicit {
margin-right: 6px;
}
.Consensus .nodeAddress {
margin-top: 4px;
}
.Consensus .first_false .legend .nodeAddress,
.Consensus .SmallRow .legend .nodeAddress,
.Consensus th.finalizedInfo .Tooltip-container {
float: none !important;
text-align: center !important;
}
.Consensus .noStretchOnLastRow::after {
content: '';
flex-grow: 1000000000;
}
.Consensus .flexContainerLargeRow .noStretchOnLastRow .firstInRow table {
width: auto !important;
}
.Consensus .flexContainerLargeRow .noStretchOnLastRow .firstInRow .emptylegend {
width: auto !important;
}
.Consensus .flexContainerLargeRow .noStretchOnLastRow .firstInRow {
width: auto !important;
}
/* similar to .App-no-telemetry */
.Consensus .noData {
width: 100vw;
line-height: 60vh;
font-size: 56px;
font-weight: 100;
text-align: center;
color: #888;
}
/* similar to .App-no-telemetry */
.Consensus .tooManyAuthorities {
width: 100vw;
line-height: 20vh;
font-size: 56px;
font-weight: 100;
text-align: center;
color: #888;
}
.Consensus svg {
z-index: 999999999;
}
@@ -0,0 +1,423 @@
import * as React from 'react';
import { Types, Maybe } from '../../common';
import { Connection } from '../../Connection';
import Measure, { BoundingRect, ContentRect } from 'react-measure';
import { ConsensusBlock } from './';
import { State as AppState } from '../../state';
import './Consensus.css';
// Maximum number of authorities the visualization is
// allowed of processing.
export const VIS_AUTHORITIES_LIMIT = 10;
export namespace Consensus {
export interface Props {
appState: Readonly<AppState>;
connection: Promise<Connection>;
}
export interface State {
dimensions: BoundingRect;
largeBlockWithLegend: BoundingRect;
largeBlock: BoundingRect;
countBlocksInLargeRow: number;
largeRowsAddFlexClass: boolean;
smallBlock: BoundingRect;
smallBlocksRows: number;
countBlocksInSmallRow: number;
smallRowsAddFlexClass: boolean;
lastConsensusInfo: string;
}
}
export class Consensus extends React.Component<Consensus.Props, {}> {
public state = {
// entire area available for rendering the visualization
dimensions: { width: -1, height: -1 } as BoundingRect,
largeBlockWithLegend: { width: -1, height: -1 } as BoundingRect,
largeBlock: { width: -1, height: -1 } as BoundingRect,
countBlocksInLargeRow: 2,
largeRowsAddFlexClass: false,
smallBlock: { width: -1, height: -1 } as BoundingRect,
smallBlocksRows: 1,
countBlocksInSmallRow: 1,
smallRowsAddFlexClass: false,
lastConsensusInfo: '',
};
public shouldComponentUpdate(
nextProps: Consensus.Props,
nextState: Consensus.State
): boolean {
if (
this.props.appState.authorities.length === 0 &&
nextProps.appState.authorities.length === 0
) {
return false;
}
this.calculateBoxCount(false);
// size detected, but flex class has not yet been added
const largeBlocksSizeDetected =
this.largeBlocksSizeDetected(nextState) === true &&
this.state.largeRowsAddFlexClass === false;
if (largeBlocksSizeDetected) {
return true;
}
const smallBlocksSizeDetected =
this.smallBlocksSizeDetected(nextState) === true &&
this.state.smallRowsAddFlexClass === false;
if (smallBlocksSizeDetected) {
return true;
}
const windowSizeChanged =
JSON.stringify(this.state.dimensions) !==
JSON.stringify(nextState.dimensions);
if (windowSizeChanged) {
return true;
}
const newConsensusInfoAvailable =
this.state.lastConsensusInfo !==
JSON.stringify(nextProps.appState.consensusInfo);
if (newConsensusInfoAvailable) {
return true;
}
const authoritySetIdDidChange =
this.props.appState.authoritySetId !== nextProps.appState.authoritySetId;
if (authoritySetIdDidChange) {
return true;
}
const authoritiesDidChange =
JSON.stringify(this.props.appState.authorities) !==
JSON.stringify(nextProps.appState.authorities);
if (authoritiesDidChange) {
return true;
}
return false;
}
public componentDidMount() {
if (this.props.appState.subscribed != null) {
const chain = this.props.appState.subscribed;
this.subscribeConsensus(chain);
}
}
public componentWillUnmount() {
if (this.props.appState.subscribed != null) {
const chain = this.props.appState.subscribed;
this.unsubscribeConsensus(chain);
}
}
public largeBlocksSizeDetected(state: Consensus.State): boolean {
// we can only state that we detected the two block sizes (with
// legend and without) if at least two blocks have been added:
// the first displayed block will always have a legend with the
// node names attached, the second not.
if (this.props.appState.consensusInfo.length < 2) {
return false;
}
// if there is more than one block then the size of the first block (with legend)
// will be different from the succeeding blocks (without legend)
return (
state.largeBlockWithLegend.width > -1 &&
state.largeBlockWithLegend.height > -1 &&
state.largeBlock.width > -1 &&
state.largeBlock.height > -1
);
}
public smallBlocksSizeDetected(state: Consensus.State): boolean {
return (
state.smallBlock.width > -1 && state.largeBlockWithLegend.height > -1
);
}
public calculateBoxCount(wasResized: boolean) {
// if the css class for flexing has already been added we don't calculate
// any box measurements then, because the box sizes would be skewed then.
if (
(wasResized || this.state.largeRowsAddFlexClass === false) &&
this.largeBlocksSizeDetected(this.state)
) {
// we need to add +2 because of the last block which doesn't contain a border.
let countBlocks =
(this.state.dimensions.width -
this.state.largeBlockWithLegend.width +
2) /
(this.state.largeBlock.width + 2);
// +1 because the firstRect was subtracted above and needs to be counted back in.
// default count is 2 because we need two blocks to measure properly (one with legend
// and one without. these measures are necessary to calculate the number of blocks
// which fit.
countBlocks =
Math.floor(countBlocks + 1) < 1 ? 2 : Math.floor(countBlocks + 1);
this.setState({
largeRowsAddFlexClass: true,
countBlocksInLargeRow: countBlocks,
});
}
if (
(wasResized || this.state.smallRowsAddFlexClass === false) &&
this.smallBlocksSizeDetected(this.state)
) {
const howManyRows = 2;
const heightLeft =
this.state.dimensions.height -
this.state.largeBlock.height * howManyRows;
let smallBlocksRows = heightLeft / this.state.smallBlock.height;
smallBlocksRows = smallBlocksRows < 1 ? 1 : Math.floor(smallBlocksRows);
let countBlocksInSmallRow =
this.state.dimensions.width / this.state.smallBlock.width;
countBlocksInSmallRow =
countBlocksInSmallRow < 1 ? 1 : Math.floor(countBlocksInSmallRow);
this.setState({
smallRowsAddFlexClass: true,
countBlocksInSmallRow,
smallBlocksRows,
});
}
}
public render() {
this.state.lastConsensusInfo = JSON.stringify(
this.props.appState.consensusInfo
);
const lastBlocks = this.props.appState.consensusInfo;
if (this.props.appState.authorities.length > VIS_AUTHORITIES_LIMIT) {
return (
<div className="Consensus">
<div className="tooManyAuthorities">
<p>Too many authorities.</p>
<p>
Won't display for more than {VIS_AUTHORITIES_LIMIT} authorities to
protect your browser.
</p>
</div>
;
</div>
);
}
if (
this.props.appState.displayConsensusLoadingScreen &&
lastBlocks.length < 2
) {
return (
<div className="Consensus">
<div className="noData">
{lastBlocks.length === 0 ? 'No ' : 'Not yet enough '}
GRANDPA data received by the authorities&hellip;
</div>
;
</div>
);
}
let from = 0;
let to = this.state.countBlocksInLargeRow;
const firstLargeRow = this.getLargeRow(lastBlocks.slice(from, to), 0);
from = to;
to = to + this.state.countBlocksInLargeRow;
const secondLargeRow = this.getLargeRow(lastBlocks.slice(from, to), 1);
from = to;
to = to + this.state.smallBlocksRows * this.state.countBlocksInSmallRow;
const smallRow = this.getSmallRow(lastBlocks.slice(from, to));
const get = (measureRef: Maybe<(ref: Element | null) => void>) => (
<div className="Consensus" ref={measureRef} key="Consensus">
{firstLargeRow}
{secondLargeRow}
{smallRow}
</div>
);
if (
!(this.state.smallRowsAddFlexClass && this.state.largeRowsAddFlexClass)
) {
return (
<React.Fragment>
<Measure bounds={true} onResize={this.handleOnResize}>
{({ measureRef }) => get(measureRef)}
</Measure>
</React.Fragment>
);
} else {
return get(null);
}
}
private handleOnResize = (contentRect: ContentRect) => {
this.setState({ dimensions: contentRect.bounds as BoundingRect });
this.calculateBoxCount(true);
};
private getAuthorities(): Types.Authority[] {
// find the node for each of these authority addresses
if (this.props.appState.authorities == null) {
return [];
}
return this.props.appState.authorities.map((address) => {
const node2 = this.props.appState.nodes
.sorted()
.filter((node) => node.validator === address)[0];
if (!node2) {
return {
Address: address,
NodeId: null,
Name: null,
} as Types.Authority;
}
return {
Address: address,
NodeId: node2.id,
Name: node2.name,
} as Types.Authority;
});
}
private getLargeRow(blocks: Types.ConsensusInfo, id: number) {
const largeBlockSizeChanged = (
isFirstBlock: boolean,
rect: BoundingRect
) => {
if (this.largeBlocksSizeDetected(this.state)) {
return;
}
if (isFirstBlock) {
this.setState({
largeBlockWithLegend: { width: rect.width, height: rect.height },
});
} else {
this.setState({
largeBlock: { width: rect.width, height: rect.height },
});
}
};
const stretchLastRowMajor =
blocks.length < this.state.countBlocksInLargeRow
? 'noStretchOnLastRow'
: '';
const flexClass = this.state.largeRowsAddFlexClass
? 'flexContainerLargeRow'
: '';
return (
<div
className={`ConsensusList LargeRow ${flexClass} ${stretchLastRowMajor}`}
key={`consensusList_${id}`}
>
{blocks.map((item, i) => {
const [height, consensusView] = item;
return (
<ConsensusBlock
changeBlocks={largeBlockSizeChanged}
firstInRow={i === 0}
lastInRow={false}
compact={false}
key={height}
height={height}
measure={!this.state.largeRowsAddFlexClass}
consensusView={consensusView}
authorities={this.getAuthorities()}
authoritySetId={this.props.appState.authoritySetId}
/>
);
})}
</div>
);
}
private getSmallRow(blocks: Types.ConsensusInfo) {
const smallBlockSizeChanged = (
_isFirstBlock: boolean,
rect: BoundingRect
) => {
if (this.smallBlocksSizeDetected(this.state)) {
return;
}
const dimensionsChanged =
this.state.smallBlock.height !== rect.height &&
this.state.smallBlock.width !== rect.width;
if (dimensionsChanged) {
this.setState({
smallBlock: { width: rect.width, height: rect.height },
});
}
};
const stretchLastRow =
blocks.length <
this.state.countBlocksInSmallRow * this.state.smallBlocksRows
? 'noStretchOnLastRow'
: '';
const classes = `ConsensusList SmallRow ${
this.state.smallRowsAddFlexClass ? 'flexContainerSmallRow' : ''
} ${stretchLastRow}`;
return (
<div className={classes} key="smallRow">
{blocks.map((item, i) => {
const [height, consensusView] = item;
let lastInRow =
(i + 1) % this.state.countBlocksInSmallRow === 0 ? true : false;
if (lastInRow && i === 0) {
// should not be marked as last one in row if it's the very first in row
lastInRow = false;
}
return (
<ConsensusBlock
changeBlocks={smallBlockSizeChanged}
firstInRow={i === 0}
lastInRow={lastInRow}
compact={true}
key={height}
height={height}
measure={!this.state.smallRowsAddFlexClass}
consensusView={consensusView}
authorities={this.getAuthorities()}
authoritySetId={this.props.appState.authoritySetId}
/>
);
})}
</div>
);
}
private async subscribeConsensus(chain: Types.ChainLabel) {
const connection = await this.props.connection;
connection.subscribeConsensus(chain);
}
private async unsubscribeConsensus(chain: Types.ChainLabel) {
const connection = await this.props.connection;
connection.unsubscribeConsensus(chain);
}
}
@@ -0,0 +1,203 @@
.Consensus .BlockConsensusMatrice {
background-color: #222;
font-family: monospace, sans-serif;
border-spacing: 0px;
border-right: 2px solid lightgrey;
border-bottom: 1px solid #999;
}
.Consensus .LargeRow .BlockConsensusMatrice:last-child {
border-right: none;
}
.Consensus .SmallRow .lastInRow {
clear: right;
width: 99%;
page-break-after: always;
}
.Consensus .BlockConsensusMatrice th {
font-weight: normal;
border-bottom: 1px dashed #999;
}
.Consensus .finalizedInfo,
.legend {
border-bottom: 1px dotted #555555;
}
.Consensus .finalizedInfo {
white-space: nowrap;
}
.Consensus .finalizedInfo .Tooltip-container {
display: inline-block;
white-space: nowrap;
vertical-align: middle;
}
.Consensus .BlockConsensusMatrice .matrice {
width: 28px;
height: 28px;
}
.Consensus .BlockConsensusMatrice .matrice {
font-weight: normal;
border-right: 1px dotted #555555;
border-bottom: 1px dotted #555;
}
.Consensus .BlockConsensusMatrice tr .matrice:last-child {
border-right: none;
}
.Consensus .BlockConsensusMatrice .matrixXLegend {
text-align: center;
border-right: 1px dotted #555555;
}
.Consensus .BlockConsensusMatrice .matrixXLegend:last-child {
border-right: none;
}
.Consensus .matrice {
text-align: center !important;
min-width: 35px;
}
.Consensus .SmallRow .matrixXLegend,
.Consensus .SmallRow .matrice {
min-width: 26px;
min-height: 26px;
}
.Consensus .finalizedInfo {
text-align: center !important;
}
.Consensus .SmallRow .finalizedInfo {
min-width: 40px;
}
.Consensus .finalizedInfo {
text-align: right;
border-right: 1px dashed #999;
min-width: 50px;
}
.Consensus .finalizedInfo .Tooltip-container {
float: none;
}
.Consensus .explicit {
fill: #e70e81;
}
.Consensus .nodeName {
float: left;
padding-right: 10px;
padding-top: 4px;
}
.Consensus .flexContainerLargeRow .firstInRow .nodeContent {
white-space: nowrap;
}
.Consensus .flexContainerLargeRow .firstInRow .nodeName {
display: inline-block !important;
float: none !important;
vertical-align: middle;
margin-bottom: 3px;
}
.Consensus .flexContainerLargeRow .firstInRow .nodeAddress {
display: inline-block !important;
float: none !important;
vertical-align: middle;
margin-right: 3px;
}
.Consensus .legend {
border-right: 1px solid #999;
white-space: nowrap;
}
.Consensus .first_false .nodeName {
display: none;
}
.Consensus .legend .nodeAddress {
float: right;
}
.Consensus .Row {
color: #999;
cursor: pointer;
}
.Consensus .Row th,
.Consensus .Row td {
text-align: left;
padding: 2px;
}
.Consensus .Row td {
position: relative;
}
.Consensus .Row .Row-truncate {
position: absolute;
left: 0;
right: 0;
top: 0;
padding: inherit;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.Consensus .Row .Row-Tooltip {
position: initial;
padding: inherit;
}
.Consensus .Row:hover {
background-color: #161616;
}
.Consensus .nodeAddress svg {
cursor: pointer;
}
.Consensus .nodeAddress svg:hover {
transform: scale(2);
}
.Consensus .matrice .Icon ~ .Icon {
margin-left: -4px;
}
.Consensus .SmallRow .matrice .Prevote svg {
margin-left: 3px;
margin-bottom: -11px;
}
.Consensus .SmallRow .matrice .Precommit svg {
margin-left: -1px;
margin-top: -6px;
margin-bottom: 0px;
}
.Consensus .jdenticonPlaceholder {
width: 28px;
float: right;
}
.Consensus .SmallRow .jdenticonPlaceholder {
width: 14px;
float: right;
}
.Consensus .even {
background-color: #333;
}
@@ -0,0 +1,335 @@
import * as React from 'react';
import Measure, { BoundingRect, ContentRect } from 'react-measure';
import { Types, Maybe } from '../../common';
import { Icon, Tooltip, PolkadotIcon } from '../';
import Jdenticon from './Jdenticon';
import checkIcon from '../../icons/check.svg';
import finalizedIcon from '../../icons/finalized.svg';
import hatchingIcon from '../../icons/hatching.svg';
import './ConsensusBlock.css';
export namespace ConsensusBlock {
export interface Props {
authorities: Types.Authority[];
authoritySetId: Maybe<Types.AuthoritySetId>;
height: Types.BlockNumber;
firstInRow: boolean;
lastInRow: boolean;
compact: boolean;
measure: boolean;
consensusView: Types.ConsensusView;
changeBlocks: (first: boolean, boundsRect: BoundingRect) => void;
}
}
export class ConsensusBlock extends React.Component<ConsensusBlock.Props, {}> {
public state = {
lastConsensusView: '',
};
public shouldComponentUpdate(nextProps: ConsensusBlock.Props): boolean {
if (
this.props.authorities.length === 0 &&
nextProps.authorities.length === 0
) {
return false;
}
const positionInfoChanged =
this.props.firstInRow !== nextProps.firstInRow ||
this.props.lastInRow !== nextProps.lastInRow;
if (positionInfoChanged) {
return true;
}
const newConsensusInfo =
JSON.stringify(nextProps.consensusView) !== this.state.lastConsensusView;
if (newConsensusInfo) {
return true;
}
return false;
}
public render() {
this.state.lastConsensusView = JSON.stringify(this.props.consensusView);
const finalizedByWhom = this.props.authorities.filter((authority) =>
this.isFinalized(authority)
);
const ratio = finalizedByWhom.length + '/' + this.props.authorities.length;
let titleFinal = <span>{ratio}</span>;
const majorityFinalized =
finalizedByWhom.length / this.props.authorities.length >= 2 / 3;
if (majorityFinalized && !this.props.compact) {
titleFinal = <span>FINAL</span>;
} else if (majorityFinalized && this.props.compact) {
const hash = this.getFinalizedHash(finalizedByWhom[0]);
titleFinal = (
<Jdenticon
hash={hash ? String(hash) : ''}
size={this.props.compact ? '14px' : '28px'}
/>
);
}
const handleOnResize = (contentRect: ContentRect) => {
this.props.changeBlocks(
this.props.firstInRow,
contentRect.bounds as BoundingRect
);
};
const get = (measureRef: Maybe<(ref: Element | null) => void>) => {
return (
<div
className={`BlockConsensusMatrice
${this.props.firstInRow ? 'firstInRow' : ''} ${
this.props.lastInRow ? 'lastInRow' : ''
}`}
key={'block_' + this.props.height}
>
<table ref={measureRef} key={'block_table_' + this.props.height}>
<thead key={'block_thead_' + this.props.height}>
<tr className="Row" key={'block_row_' + this.props.height}>
{this.props.firstInRow && !this.props.compact ? (
<th
className="emptylegend"
key={'block_row_' + this.props.height + '_empty'}
>
&nbsp;
</th>
) : null}
<th
className="legend"
key={'block_row_' + this.props.height + '_legend'}
>
<Tooltip text={`Block number: ${this.props.height}`}>
{this.displayBlockNumber()}
</Tooltip>
</th>
<th
className="finalizedInfo"
key={'block_row_' + this.props.height + '_finalized_info'}
>
{titleFinal}
</th>
{this.props.authorities.map((authority) => (
<th
className="matrixXLegend"
key={`${this.props.height}_matrice_x_${authority.Address}`}
>
{this.getAuthorityContent(authority)}
</th>
))}
</tr>
</thead>
<tbody key={'block_row_' + this.props.height + '_tbody'}>
{this.props.authorities.map((authority, row) =>
this.renderMatriceRow(authority, this.props.authorities, row)
)}
</tbody>
</table>
</div>
);
};
if (this.props.measure) {
return (
<Measure bounds={true} onResize={handleOnResize}>
{({ measureRef }) => get(measureRef)}
</Measure>
);
} else {
return get(null);
}
}
private displayBlockNumber(): string {
const blockNumber = String(this.props.height);
return blockNumber.length > 2
? '…' + blockNumber.substr(blockNumber.length - 2, blockNumber.length)
: blockNumber;
}
private isFinalized(authority: Types.Authority): boolean {
if (!authority || authority.NodeId == null || authority.Address == null) {
return false;
}
const { Address: addr } = authority;
const consensus = this.props.consensusView;
return (
consensus != null &&
addr in consensus &&
addr in consensus[addr] &&
consensus[addr][addr].Finalized === true
);
}
private getFinalizedHash(authority: Types.Authority): Maybe<Types.BlockHash> {
if (this.isFinalized(authority)) {
const { Address: addr } = authority;
return this.props.consensusView[addr][addr].FinalizedHash;
}
return null;
}
private renderMatriceRow(
authority: Types.Authority,
authorities: Types.Authority[],
row: number
): JSX.Element {
let finalizedInfo = <span>&nbsp;</span>;
let finalizedHash;
if (authority.NodeId != null && this.isFinalized(authority)) {
const matrice = this.props.consensusView[authority.Address][
authority.Address
];
finalizedInfo = matrice.ImplicitFinalized ? (
<Icon className="implicit" src={finalizedIcon} alt="" />
) : (
<Icon className="explicit" src={finalizedIcon} alt="" />
);
finalizedHash = matrice.FinalizedHash ? (
<Jdenticon hash={matrice.FinalizedHash} size="28px" />
) : (
<div className="jdenticonPlaceholder">&nbsp;</div>
);
}
const name = authority.Name ? (
<span>{authority.Name}</span>
) : (
<em>no data received from node</em>
);
const firstName = this.props.firstInRow ? (
<td key={'name_' + name} className="nameLegend">
{name}
</td>
) : (
''
);
return (
<tr className="Row" key={'block_row_' + this.props.height + '_' + row}>
{firstName}
<td
className="legend"
key={'block_row_' + this.props.height + '_' + row + '_legend'}
>
{this.getAuthorityContent(authority)}
</td>
<td
className="finalizedInfo"
key={'block_row_' + this.props.height + '_' + row + '_finalizedInfo'}
>
{finalizedInfo}
{finalizedHash}
</td>
{authorities.map((columnNode, column) => {
const evenOdd = ((row % 2) + column) % 2 === 0 ? 'even' : 'odd';
return (
<td
key={
'matrice_' +
this.props.height +
'_' +
row +
'_' +
authority.Address +
'_' +
columnNode.Address
}
className={`matrice ${evenOdd}`}
>
{this.getCellContent(authority, columnNode)}
</td>
);
})}
</tr>
);
}
private getAuthorityContent(authority: Types.Authority): JSX.Element {
return (
<div
className="nodeContent"
key={'authority_' + this.props.height + '_' + authority.Address}
>
<div className="nodeAddress" key={'authority_' + authority.Address}>
<PolkadotIcon
account={authority.Address}
size={this.props.compact ? 14 : 28}
/>
</div>
</div>
);
}
private getCellContent(
rowAuthority: Types.Authority,
columnAuthority: Types.Authority
) {
const consensusInfo =
this.props.consensusView &&
rowAuthority.Address &&
rowAuthority.Address in this.props.consensusView &&
columnAuthority.Address in this.props.consensusView[rowAuthority.Address]
? this.props.consensusView[rowAuthority.Address][
columnAuthority.Address
]
: null;
const prevote = consensusInfo && consensusInfo.Prevote;
const implicitPrevote = consensusInfo && consensusInfo.ImplicitPrevote;
const precommit = consensusInfo && consensusInfo.Precommit;
const implicitPrecommit = consensusInfo && consensusInfo.ImplicitPrecommit;
if (rowAuthority.Address !== columnAuthority.Address) {
let statPrevote;
let statPrecommit;
if (implicitPrevote) {
statPrevote = (
<Icon src={checkIcon} className="implicit" alt="Implicit Prevote" />
);
}
if (implicitPrecommit) {
statPrecommit = (
<Icon src={checkIcon} className="implicit" alt="Implicit Precommit" />
);
}
if (prevote) {
statPrevote = (
<Icon src={checkIcon} className="explicit" alt="Prevote" />
);
}
if (precommit) {
statPrecommit = (
<Icon src={checkIcon} className="explicit" alt="Precommit" />
);
}
return (
<span key={'icons_pre'}>
{statPrevote}
{statPrecommit}
</span>
);
} else {
return <Icon src={hatchingIcon} className="hatching" alt="" />;
}
}
}
@@ -0,0 +1,9 @@
.Jdenticon {
cursor: pointer;
vertical-align: middle;
background-color: #fff;
}
.Jdenticon:hover {
transform: scale(2);
}
@@ -0,0 +1,45 @@
import * as React from 'react';
import './Jdenticon.css';
export interface Props {
hash: string;
size: string;
}
class Jdenticon extends React.Component<Props, {}> {
private element = null;
public componentDidUpdate() {
const jdenticon = (window as any).jdenticon;
if (jdenticon) {
jdenticon.update(this.element);
}
}
public componentDidMount() {
const jdenticon = (window as any).jdenticon;
if (jdenticon) {
jdenticon.update(this.element);
}
}
public render() {
const { hash, size } = this.props;
return (
<svg
className="Jdenticon"
ref={(element) => this.handleRef(element)}
width={size}
height={size}
data-jdenticon-value={hash}
/>
);
}
private handleRef(element: any) {
this.element = element;
}
}
export default Jdenticon;
@@ -0,0 +1,2 @@
export * from './Consensus';
export * from './ConsensusBlock';