mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-04-30 09:38:01 +00:00
ebb01c1a7d
* More responsive React updates on scroll * `Icon`s now use shadow dom * Faster Sparkline * Recycle table rows * Separate Header from Chain to avoid vdom diffing * Separate THead from Row.HEADER to avoid vdom diffing * Throttle rendering updates on chain tabs, also styles * Minor tweaks and fixes * Created components for all columns * Wrapping up Column refactor * Rename Row--td to Column * Lazy `Ago` * Update styles for faster layouting * Minor cleanup * Fix Connection * Use shadow DOM in `PolkadotIcon` * Comments and tweaks for the List component * Faster Tooltip and Truncate * Minor tweaks * Tooltiped columns can now be copied * Future-proof Connection * Remove the <div> wrapper from Icon * Fix dash on missing graph data * Clean up some SVGs * Cleanup and comments * Localize the use of `previousKeys` to `recalculateKeys` * Custom appState disjoint from React component state * Make appState and appUpdate refs readonly * Cleanup
328 lines
9.5 KiB
TypeScript
328 lines
9.5 KiB
TypeScript
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'}
|
|
>
|
|
|
|
</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> </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} />
|
|
) : (
|
|
<Icon className="explicit" src={finalizedIcon} />
|
|
);
|
|
|
|
finalizedHash = matrice.FinalizedHash ? (
|
|
<Jdenticon hash={matrice.FinalizedHash} size="28px" />
|
|
) : (
|
|
<div className="jdenticonPlaceholder"> </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" />;
|
|
}
|
|
if (implicitPrecommit) {
|
|
statPrecommit = <Icon src={checkIcon} className="implicit" />;
|
|
}
|
|
|
|
if (prevote) {
|
|
statPrevote = <Icon src={checkIcon} className="explicit" />;
|
|
}
|
|
if (precommit) {
|
|
statPrecommit = <Icon src={checkIcon} className="explicit" />;
|
|
}
|
|
|
|
return (
|
|
<span key={'icons_pre'}>
|
|
{statPrevote}
|
|
{statPrecommit}
|
|
</span>
|
|
);
|
|
} else {
|
|
return <Icon src={hatchingIcon} className="hatching" />;
|
|
}
|
|
}
|
|
}
|