mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-06-11 23:31:08 +00:00
Add chains overlay (#157)
* feat: Adding a show-all-chains button * feat: Show all-chains overlay selector
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import { Types, SortedCollection } from '@dotstats/common';
|
||||
import { Chains, Chain, Ago, OfflineIndicator } from './components';
|
||||
import { AllChains, Chains, Chain, Ago, OfflineIndicator } from './components';
|
||||
import { Connection } from './Connection';
|
||||
import { PersistentObject, PersistentSet } from './persist';
|
||||
import { State, Node } from './state';
|
||||
import { State, Node, ChainData } from './state';
|
||||
import { getHashData } from './utils';
|
||||
import stable from 'stable';
|
||||
|
||||
import './App.css';
|
||||
|
||||
@@ -49,6 +51,8 @@ export default class App extends React.Component<{}, State> {
|
||||
this.setState({ nodes, pins });
|
||||
});
|
||||
|
||||
const { tab = '' } = getHashData();
|
||||
|
||||
this.state = {
|
||||
status: 'offline',
|
||||
best: 0 as Types.BlockNumber,
|
||||
@@ -66,7 +70,7 @@ export default class App extends React.Component<{}, State> {
|
||||
nodes: new SortedCollection(Node.compare),
|
||||
settings: this.settings.raw(),
|
||||
pins: this.pins.get(),
|
||||
tabChanged: false,
|
||||
tab,
|
||||
};
|
||||
|
||||
this.connection = Connection.create(this.pins, (changes) => {
|
||||
@@ -79,11 +83,12 @@ export default class App extends React.Component<{}, State> {
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { chains, timeDiff, subscribed, status } = this.state;
|
||||
const { timeDiff, subscribed, status, tab } = this.state;
|
||||
const chains = this.chains();
|
||||
|
||||
Ago.timeDiff = timeDiff;
|
||||
|
||||
if (chains.size === 0) {
|
||||
if (chains.length === 0) {
|
||||
return (
|
||||
<div className="App App-no-telemetry">
|
||||
<OfflineIndicator status={status} />
|
||||
@@ -92,27 +97,28 @@ export default class App extends React.Component<{}, State> {
|
||||
);
|
||||
}
|
||||
|
||||
const overlay = tab === 'all-chains'
|
||||
? <AllChains chains={chains} subscribed={subscribed} connection={this.connection} />
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<OfflineIndicator status={status} />
|
||||
<Chains chains={chains} subscribed={subscribed} connection={this.connection} />
|
||||
<Chain appState={this.state} connection={this.connection} settings={this.settings} pins={this.pins} />
|
||||
{overlay}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
if (this.state.tabChanged === true) {
|
||||
this.setState({tabChanged: false});
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillMount() {
|
||||
window.addEventListener('keydown', this.onKeyPress);
|
||||
window.addEventListener('hashchange', this.onHashChange);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
window.removeEventListener('keydown', this.onKeyPress);
|
||||
window.removeEventListener('hashchange', this.onHashChange);
|
||||
}
|
||||
|
||||
private onKeyPress = (event: KeyboardEvent) => {
|
||||
@@ -140,4 +146,30 @@ export default class App extends React.Component<{}, State> {
|
||||
connection.subscribe(chains[index]);
|
||||
})
|
||||
}
|
||||
|
||||
private onHashChange = (event: Event) => {
|
||||
const { tab = '' } = getHashData();
|
||||
|
||||
this.setState({ tab });
|
||||
}
|
||||
|
||||
private chains(): ChainData[] {
|
||||
return stable
|
||||
.inplace(
|
||||
Array.from(this.state.chains.entries()),
|
||||
(a, b) => {
|
||||
if (a[0] === 'Alexander') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b[0] === 'Alexander') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return b[1] - a[1];
|
||||
}
|
||||
)
|
||||
.map(([label, nodeCount]) => ({ label, nodeCount }));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export class Connection {
|
||||
public subscribe(chain: Types.ChainLabel) {
|
||||
if (this.state.subscribed != null && this.state.subscribed !== chain) {
|
||||
this.state = this.update({
|
||||
tabChanged: true,
|
||||
tab: 'list',
|
||||
});
|
||||
setHashData({ chain, tab: 'list' });
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
.AllChains {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
top: 16px;
|
||||
bottom: 16px;
|
||||
left: 50%;
|
||||
margin: 0 0 0 -150px;
|
||||
width: 25vw;
|
||||
min-width: 300px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 20px rgba(0,0,0,0.35);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hide;
|
||||
}
|
||||
|
||||
.AllChains-chain {
|
||||
padding: 0 12px;
|
||||
background: #B5AEAE;
|
||||
color: #444;
|
||||
display: block;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.5);
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.AllChains-node-count {
|
||||
display: inline-block;
|
||||
padding: 0 0.5em 0.1em;
|
||||
border-radius: 1em;
|
||||
background: #8C8787;
|
||||
color: #fff;
|
||||
font-weight: normal;
|
||||
text-shadow: rgba(0,0,0,0.5) 0 1px 0;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.4em;
|
||||
margin: 0 -0.3em 0 0.3em;
|
||||
}
|
||||
|
||||
.AllChains-chain-selected {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.AllChains-chain-selected .AllChains-node-count {
|
||||
background: #E6007A;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import * as React from 'react';
|
||||
import { Connection } from '../Connection';
|
||||
import { Types, Maybe } from '@dotstats/common';
|
||||
import { ChainData } from '../state';
|
||||
|
||||
import './AllChains.css';
|
||||
|
||||
export namespace AllChains {
|
||||
export interface Props {
|
||||
chains: ChainData[],
|
||||
subscribed: Maybe<Types.ChainLabel>,
|
||||
connection: Promise<Connection>
|
||||
}
|
||||
}
|
||||
|
||||
export class AllChains extends React.Component<AllChains.Props, {}> {
|
||||
public render() {
|
||||
const { chains } = this.props;
|
||||
|
||||
return (
|
||||
<div className="AllChains">
|
||||
{chains.map((chain) => this.renderChain(chain))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderChain(chain: ChainData): React.ReactNode {
|
||||
const { label, nodeCount } = chain;
|
||||
|
||||
const className = label === this.props.subscribed
|
||||
? 'AllChains-chain AllChains-chain-selected'
|
||||
: 'AllChains-chain';
|
||||
|
||||
return (
|
||||
<a key={label} className={className} onClick={this.subscribe.bind(this, label)}>
|
||||
{label} <span className="AllChains-node-count" title="Node Count">{nodeCount}</span>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
private async subscribe(chain: Types.ChainLabel) {
|
||||
const connection = await this.props.connection;
|
||||
|
||||
connection.subscribe(chain);
|
||||
connection.resetConsensus();
|
||||
}
|
||||
}
|
||||
@@ -56,13 +56,6 @@ export class Chain extends React.Component<Chain.Props, Chain.State> {
|
||||
};
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: Chain.Props, nextState: Chain.State): boolean {
|
||||
if (nextProps.appState.tabChanged === true && nextState.display === 'consensus') {
|
||||
this.setDisplay('list');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
const { best, finalized, blockTimestamp, blockAverage } = appState;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.Chains {
|
||||
background: #B5AEAE;
|
||||
color: #000;
|
||||
padding: 0 16px;
|
||||
padding: 0 76px 0 16px;
|
||||
height: 40px;
|
||||
min-width: 1318px;
|
||||
position: relative;
|
||||
@@ -25,6 +25,15 @@
|
||||
border-left: 1px solid rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
.Chains-all-chains {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
right: 48px;
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
.Chains-fork-me {
|
||||
display: block;
|
||||
padding: 0;
|
||||
@@ -34,7 +43,7 @@
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
.Chains-fork-me .Icon {
|
||||
.Chains-all-chains .Icon, .Chains-fork-me .Icon {
|
||||
font-size: 28px;
|
||||
margin: 0;
|
||||
height: 28px;
|
||||
@@ -58,7 +67,6 @@
|
||||
.Chains-chain-selected {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.Chains-chain-selected .Chains-node-count {
|
||||
|
||||
@@ -2,19 +2,15 @@ import * as React from 'react';
|
||||
import { Connection } from '../Connection';
|
||||
import { Icon } from './Icon';
|
||||
import { Types, Maybe } from '@dotstats/common';
|
||||
import stable from 'stable';
|
||||
import { ChainData } from '../state';
|
||||
|
||||
import githubIcon from '../icons/mark-github.svg';
|
||||
import listIcon from '../icons/three-bars.svg';
|
||||
import './Chains.css';
|
||||
|
||||
interface ChainData {
|
||||
label: Types.ChainLabel;
|
||||
nodeCount: Types.NodeCount;
|
||||
}
|
||||
|
||||
export namespace Chains {
|
||||
export interface Props {
|
||||
chains: Map<Types.ChainLabel, Types.NodeCount>,
|
||||
chains: ChainData[],
|
||||
subscribed: Maybe<Types.ChainLabel>,
|
||||
connection: Promise<Connection>
|
||||
}
|
||||
@@ -22,11 +18,15 @@ export namespace Chains {
|
||||
|
||||
export class Chains extends React.Component<Chains.Props, {}> {
|
||||
public render() {
|
||||
const allChainsHref = this.props.subscribed ? `#all-chains/${this.props.subscribed}` : `#all-chains`;
|
||||
const { chains } = this.props;
|
||||
|
||||
return (
|
||||
<div className="Chains">
|
||||
{
|
||||
this.chains.map((chain) => this.renderChain(chain))
|
||||
}
|
||||
{chains.map((chain) => this.renderChain(chain))}
|
||||
<a className="Chains-all-chains" href={allChainsHref}>
|
||||
<Icon src={listIcon} alt="All Chains" />
|
||||
</a>
|
||||
<a className="Chains-fork-me" href="https://github.com/paritytech/substrate-telemetry" target="_blank">
|
||||
<Icon src={githubIcon} alt="Fork Me!" />
|
||||
</a>
|
||||
@@ -48,25 +48,6 @@ export class Chains extends React.Component<Chains.Props, {}> {
|
||||
)
|
||||
}
|
||||
|
||||
private get chains(): ChainData[] {
|
||||
return stable
|
||||
.inplace(
|
||||
Array.from(this.props.chains.entries()),
|
||||
(a, b) => {
|
||||
if (a[0] === 'Alexander') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b[0] === 'Alexander') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return b[1] - a[1];
|
||||
}
|
||||
)
|
||||
.map(([label, nodeCount]) => ({ label, nodeCount }));
|
||||
}
|
||||
|
||||
private async subscribe(chain: Types.ChainLabel) {
|
||||
const connection = await this.props.connection;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './AllChains';
|
||||
export * from './Chains';
|
||||
export * from './Chain';
|
||||
export * from './List';
|
||||
|
||||
@@ -195,7 +195,7 @@ export interface State {
|
||||
finalized: Types.BlockNumber;
|
||||
consensusInfo: Types.ConsensusInfo;
|
||||
displayConsensusLoadingScreen: boolean;
|
||||
tabChanged: boolean;
|
||||
tab: string;
|
||||
authorities: Types.Address[];
|
||||
authoritySetId: Maybe<Types.AuthoritySetId>;
|
||||
sendFinality: boolean;
|
||||
@@ -211,3 +211,8 @@ export interface State {
|
||||
|
||||
export type Update = <K extends keyof State>(changes: Pick<State, K> | null) => Readonly<State>;
|
||||
export type UpdateBound = <K extends keyof State>(changes: Pick<State, K> | null) => void;
|
||||
|
||||
export interface ChainData {
|
||||
label: Types.ChainLabel;
|
||||
nodeCount: Types.NodeCount;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user