diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx
index 36fc4ee..b60b4eb 100644
--- a/packages/frontend/src/App.tsx
+++ b/packages/frontend/src/App.tsx
@@ -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 (
@@ -92,27 +97,28 @@ export default class App extends React.Component<{}, State> {
);
}
+ const overlay = tab === 'all-chains'
+ ?
+ : null;
+
return (
+ {overlay}
);
}
- 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 }));
+ }
+
}
diff --git a/packages/frontend/src/Connection.ts b/packages/frontend/src/Connection.ts
index 1db0c65..e23199f 100644
--- a/packages/frontend/src/Connection.ts
+++ b/packages/frontend/src/Connection.ts
@@ -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 {
diff --git a/packages/frontend/src/components/AllChains.css b/packages/frontend/src/components/AllChains.css
new file mode 100644
index 0000000..c73d8ec
--- /dev/null
+++ b/packages/frontend/src/components/AllChains.css
@@ -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;
+}
diff --git a/packages/frontend/src/components/AllChains.tsx b/packages/frontend/src/components/AllChains.tsx
new file mode 100644
index 0000000..e5c53d4
--- /dev/null
+++ b/packages/frontend/src/components/AllChains.tsx
@@ -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
,
+ connection: Promise
+ }
+}
+
+export class AllChains extends React.Component {
+ public render() {
+ const { chains } = this.props;
+
+ return (
+
+ {chains.map((chain) => this.renderChain(chain))}
+
+ );
+ }
+
+ private renderChain(chain: ChainData): React.ReactNode {
+ const { label, nodeCount } = chain;
+
+ const className = label === this.props.subscribed
+ ? 'AllChains-chain AllChains-chain-selected'
+ : 'AllChains-chain';
+
+ return (
+
+ {label} {nodeCount}
+
+ )
+ }
+
+ private async subscribe(chain: Types.ChainLabel) {
+ const connection = await this.props.connection;
+
+ connection.subscribe(chain);
+ connection.resetConsensus();
+ }
+}
diff --git a/packages/frontend/src/components/Chain/Chain.tsx b/packages/frontend/src/components/Chain/Chain.tsx
index c08ca36..0c8db16 100644
--- a/packages/frontend/src/components/Chain/Chain.tsx
+++ b/packages/frontend/src/components/Chain/Chain.tsx
@@ -56,13 +56,6 @@ export class Chain extends React.Component {
};
}
- 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;
diff --git a/packages/frontend/src/components/Chains.css b/packages/frontend/src/components/Chains.css
index 2d8188e..d6eced2 100644
--- a/packages/frontend/src/components/Chains.css
+++ b/packages/frontend/src/components/Chains.css
@@ -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 {
diff --git a/packages/frontend/src/components/Chains.tsx b/packages/frontend/src/components/Chains.tsx
index 0d54d53..2891d19 100644
--- a/packages/frontend/src/components/Chains.tsx
+++ b/packages/frontend/src/components/Chains.tsx
@@ -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,
+ chains: ChainData[],
subscribed: Maybe,
connection: Promise
}
@@ -22,11 +18,15 @@ export namespace Chains {
export class Chains extends React.Component {
public render() {
+ const allChainsHref = this.props.subscribed ? `#all-chains/${this.props.subscribed}` : `#all-chains`;
+ const { chains } = this.props;
+
return (
- {
- this.chains.map((chain) => this.renderChain(chain))
- }
+ {chains.map((chain) => this.renderChain(chain))}
+
+
+
@@ -48,25 +48,6 @@ export class Chains extends React.Component
{
)
}
- 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;
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index 93000ba..ebcf4d2 100644
--- a/packages/frontend/src/components/index.ts
+++ b/packages/frontend/src/components/index.ts
@@ -1,3 +1,4 @@
+export * from './AllChains';
export * from './Chains';
export * from './Chain';
export * from './List';
diff --git a/packages/frontend/src/state.ts b/packages/frontend/src/state.ts
index 2b4c95f..5446c60 100644
--- a/packages/frontend/src/state.ts
+++ b/packages/frontend/src/state.ts
@@ -195,7 +195,7 @@ export interface State {
finalized: Types.BlockNumber;
consensusInfo: Types.ConsensusInfo;
displayConsensusLoadingScreen: boolean;
- tabChanged: boolean;
+ tab: string;
authorities: Types.Address[];
authoritySetId: Maybe;
sendFinality: boolean;
@@ -211,3 +211,8 @@ export interface State {
export type Update = (changes: Pick | null) => Readonly;
export type UpdateBound = (changes: Pick | null) => void;
+
+export interface ChainData {
+ label: Types.ChainLabel;
+ nodeCount: Types.NodeCount;
+}