Update frontend dependencies (#498)
* update npm packages * update tsconfig * remove babelrc, babel presets as well as stable package * fix svg namespace tag syntax errors * fix reference error due to namespace and class component having same name * replace tslint with eslint * make eslint happier * update .nvmrc to 14 * update node version to 14 in gh workflow * fix eslint warnings due to warnings treated as errors on CI (process.env.CI = true) * pretty fix * bump node version in Dockerfile * use createRoot instead of react-dom render * update browsers list in package.json
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x]
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"presets": ["env", "react"]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react", "react-hooks", "@typescript-eslint"],
|
||||
"rules": {
|
||||
"no-console": 0,
|
||||
"no-empty": 0,
|
||||
"no-bitwise": 0,
|
||||
"quotes": [1, "single"],
|
||||
"jsx-quotes": [1, "prefer-double"],
|
||||
"semi": [1, "always"],
|
||||
"@typescript-eslint/prefer-as-const": 1,
|
||||
"@typescript-eslint/no-namespace": 1,
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"caughtErrorsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
12
|
||||
14
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#### BUILDER IMAGE ####
|
||||
FROM docker.io/node:12 as builder
|
||||
FROM docker.io/node:14 as builder
|
||||
LABEL maintainer="Chevdor <chevdor@gmail.com>"
|
||||
LABEL description="Substrate Telemetry Frontend builder image"
|
||||
|
||||
|
||||
@@ -6,44 +6,45 @@
|
||||
"description": "Polkadot Telemetry frontend",
|
||||
"scripts": {
|
||||
"precommit": "lint-staged",
|
||||
"start": "react-scripts-ts start",
|
||||
"build": "react-scripts-ts build",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "TS_NODE_PROJECT=tsconfig.test.json tape -r ts-node/register test/**/*spec.ts",
|
||||
"test:coverage": "nyc yarn test",
|
||||
"eject": "react-scripts-ts eject",
|
||||
"eject": "react-scripts eject",
|
||||
"pretty:check": "prettier --check src/**/*.{ts,tsx}",
|
||||
"pretty:fix": "prettier --write src",
|
||||
"clean": "rm -rf node_modules build .nyc ./tmp/env-config.js report*.json yarn-error.log"
|
||||
"clean": "rm -rf node_modules build .nyc ./tmp/env-config.js report*.json yarn-error.log",
|
||||
"lint": "eslint src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@polkadot/util-crypto": "^2.8.1",
|
||||
"@polkadot/util-crypto": "^10.1.7",
|
||||
"@types/react-measure": "^2.0.6",
|
||||
"blakejs": "^1.1.0",
|
||||
"husky": "^4.2.5",
|
||||
"lint-staged": "^10.1.7",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-measure": "^2.3.0",
|
||||
"react-scripts-ts": "3.1.0",
|
||||
"stable": "^0.1.8",
|
||||
"tslint": "^6.1.1"
|
||||
"blakejs": "^1.2.0",
|
||||
"husky": "^8.0.1",
|
||||
"lint-staged": "^13.0.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-measure": "^2.5.2",
|
||||
"react-scripts": "5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^13.13.2",
|
||||
"@types/react": "^16.9.34",
|
||||
"@types/react-dom": "^16.9.6",
|
||||
"@types/node": "^16.11.58",
|
||||
"@types/react": "^18.0.18",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/tape": "^4.13.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.36.2",
|
||||
"@typescript-eslint/parser": "^5.36.2",
|
||||
"eslint": "^8.23.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.31.8",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"nyc": "^15.0.1",
|
||||
"prettier": "^2.0.5",
|
||||
"tape": "^4.13.2",
|
||||
"ts-node": "^8.9.0",
|
||||
"tslint": "^6.1.1",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"tslint-plugin-prettier": "^2.3.0",
|
||||
"typescript": "^4.4.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
@@ -51,15 +52,18 @@
|
||||
"pretty:fix"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"chalk": "^2.4.2",
|
||||
"node-forge": ">=0.10.0",
|
||||
"yargs-parser": ">=13.1.2",
|
||||
"dot-prop": ">=4.2.1",
|
||||
"lodash": ">=4.17.19",
|
||||
"serialize-javascript": ">=3.1.0",
|
||||
"js-yaml": ">=3.13.1",
|
||||
"webpack-dev-server": "^2.11.4",
|
||||
"mem": ">=4.0.0"
|
||||
"browserslist": {
|
||||
"production": [
|
||||
"chrome >= 67",
|
||||
"edge >= 79",
|
||||
"firefox >= 68",
|
||||
"opera >= 54",
|
||||
"safari >= 14"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,23 +27,23 @@ import {
|
||||
Node,
|
||||
ChainData,
|
||||
comparePinnedChains,
|
||||
StateSettings,
|
||||
} from './state';
|
||||
import { getHashData } from './utils';
|
||||
import stable from 'stable';
|
||||
|
||||
import './App.css';
|
||||
|
||||
export default class App extends React.Component<{}, {}> {
|
||||
export default class App extends React.Component {
|
||||
private chainsCache: ChainData[] = [];
|
||||
// Custom state for finer control over updates
|
||||
private readonly appState: Readonly<State>;
|
||||
private readonly appUpdate: Update;
|
||||
private readonly settings: PersistentObject<State.Settings>;
|
||||
private readonly settings: PersistentObject<StateSettings>;
|
||||
private readonly pins: PersistentSet<Types.NodeName>;
|
||||
private readonly sortBy: Persistent<Maybe<number>>;
|
||||
private readonly connection: Promise<Connection>;
|
||||
|
||||
constructor(props: {}) {
|
||||
constructor(props: Record<string, unknown>) {
|
||||
super(props);
|
||||
|
||||
this.settings = new PersistentObject(
|
||||
@@ -225,8 +225,7 @@ export default class App extends React.Component<{}, {}> {
|
||||
return this.chainsCache;
|
||||
}
|
||||
|
||||
this.chainsCache = stable.inplace(
|
||||
Array.from(this.appState.chains.values()),
|
||||
this.chainsCache = Array.from(this.appState.chains.values()).sort(
|
||||
(a, b) => {
|
||||
const pinned = comparePinnedChains(a.genesisHash, b.genesisHash);
|
||||
|
||||
@@ -241,7 +240,7 @@ export default class App extends React.Component<{}, {}> {
|
||||
return this.chainsCache;
|
||||
}
|
||||
|
||||
private selectedColumns(settings: State.Settings): Column[] {
|
||||
private selectedColumns(settings: StateSettings): Column[] {
|
||||
return Row.columns.filter(
|
||||
({ setting }) => setting == null || settings[setting]
|
||||
);
|
||||
|
||||
@@ -68,7 +68,7 @@ export class Connection {
|
||||
return `wss://${window.location.hostname}/feed/`;
|
||||
}
|
||||
|
||||
return `ws://127.0.0.1:8000/feed`;
|
||||
return 'ws://127.0.0.1:8000/feed';
|
||||
}
|
||||
|
||||
private static async socket(): Promise<WebSocket> {
|
||||
@@ -89,7 +89,7 @@ export class Connection {
|
||||
}
|
||||
|
||||
private static async trySocket(): Promise<Maybe<WebSocket>> {
|
||||
return new Promise<Maybe<WebSocket>>((resolve, _) => {
|
||||
return new Promise<Maybe<WebSocket>>((resolve) => {
|
||||
function clean() {
|
||||
socket.removeEventListener('open', onSuccess);
|
||||
socket.removeEventListener('close', onFailure);
|
||||
@@ -469,7 +469,8 @@ export class Connection {
|
||||
let data: FeedMessage.Data;
|
||||
|
||||
if (typeof event.data === 'string') {
|
||||
data = (event.data as any) as FeedMessage.Data;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data = event.data as any as FeedMessage.Data;
|
||||
} else {
|
||||
const u8aData = new Uint8Array(event.data);
|
||||
|
||||
@@ -480,7 +481,8 @@ export class Connection {
|
||||
|
||||
const str = Connection.utf8decoder.decode(event.data);
|
||||
|
||||
data = (str as any) as FeedMessage.Data;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data = str as any as FeedMessage.Data;
|
||||
}
|
||||
|
||||
this.handleMessages(FeedMessage.deserialize(data));
|
||||
|
||||
@@ -109,9 +109,7 @@ export function sortedIndexOf<T>(
|
||||
return -1;
|
||||
}
|
||||
|
||||
export namespace SortedCollection {
|
||||
export type StateRef = Opaque<number, 'SortedCollection.StateRef'>;
|
||||
}
|
||||
type StateRef = Opaque<number, 'SortedCollection.StateRef'>;
|
||||
|
||||
interface Focus {
|
||||
start: number;
|
||||
@@ -268,12 +266,12 @@ export class SortedCollection<Item extends { id: number }> {
|
||||
}
|
||||
|
||||
// Get the reference to current ordering state of focused items.
|
||||
public get ref(): SortedCollection.StateRef {
|
||||
return this.changeRef as SortedCollection.StateRef;
|
||||
public get ref(): StateRef {
|
||||
return this.changeRef as StateRef;
|
||||
}
|
||||
|
||||
// Check if order of focused items has changed since obtaining a `ref`.
|
||||
public hasChangedSince(ref: SortedCollection.StateRef): boolean {
|
||||
public hasChangedSince(ref: StateRef): boolean {
|
||||
return this.changeRef > ref;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,194 +41,192 @@ import {
|
||||
} from './types';
|
||||
|
||||
export const ACTIONS = {
|
||||
FeedVersion: 0x00 as 0x00,
|
||||
BestBlock: 0x01 as 0x01,
|
||||
BestFinalized: 0x02 as 0x02,
|
||||
AddedNode: 0x03 as 0x03,
|
||||
RemovedNode: 0x04 as 0x04,
|
||||
LocatedNode: 0x05 as 0x05,
|
||||
ImportedBlock: 0x06 as 0x06,
|
||||
FinalizedBlock: 0x07 as 0x07,
|
||||
NodeStats: 0x08 as 0x08,
|
||||
NodeHardware: 0x09 as 0x09,
|
||||
TimeSync: 0x0a as 0x0a,
|
||||
AddedChain: 0x0b as 0x0b,
|
||||
RemovedChain: 0x0c as 0x0c,
|
||||
SubscribedTo: 0x0d as 0x0d,
|
||||
UnsubscribedFrom: 0x0e as 0x0e,
|
||||
Pong: 0x0f as 0x0f,
|
||||
AfgFinalized: 0x10 as 0x10,
|
||||
AfgReceivedPrevote: 0x11 as 0x11,
|
||||
AfgReceivedPrecommit: 0x12 as 0x12,
|
||||
AfgAuthoritySet: 0x13 as 0x13,
|
||||
StaleNode: 0x14 as 0x14,
|
||||
NodeIO: 0x15 as 0x15,
|
||||
ChainStatsUpdate: 0x16 as 0x16,
|
||||
FeedVersion: 0x00 as const,
|
||||
BestBlock: 0x01 as const,
|
||||
BestFinalized: 0x02 as const,
|
||||
AddedNode: 0x03 as const,
|
||||
RemovedNode: 0x04 as const,
|
||||
LocatedNode: 0x05 as const,
|
||||
ImportedBlock: 0x06 as const,
|
||||
FinalizedBlock: 0x07 as const,
|
||||
NodeStats: 0x08 as const,
|
||||
NodeHardware: 0x09 as const,
|
||||
TimeSync: 0x0a as const,
|
||||
AddedChain: 0x0b as const,
|
||||
RemovedChain: 0x0c as const,
|
||||
SubscribedTo: 0x0d as const,
|
||||
UnsubscribedFrom: 0x0e as const,
|
||||
Pong: 0x0f as const,
|
||||
AfgFinalized: 0x10 as const,
|
||||
AfgReceivedPrevote: 0x11 as const,
|
||||
AfgReceivedPrecommit: 0x12 as const,
|
||||
AfgAuthoritySet: 0x13 as const,
|
||||
StaleNode: 0x14 as const,
|
||||
NodeIO: 0x15 as const,
|
||||
ChainStatsUpdate: 0x16 as const,
|
||||
};
|
||||
|
||||
export type Action = typeof ACTIONS[keyof typeof ACTIONS];
|
||||
export type Payload = Message['payload'];
|
||||
|
||||
export namespace Variants {
|
||||
export interface MessageBase {
|
||||
action: Action;
|
||||
}
|
||||
interface MessageBase {
|
||||
action: Action;
|
||||
}
|
||||
|
||||
export interface FeedVersionMessage extends MessageBase {
|
||||
action: typeof ACTIONS.FeedVersion;
|
||||
payload: FeedVersion;
|
||||
}
|
||||
interface FeedVersionMessage extends MessageBase {
|
||||
action: typeof ACTIONS.FeedVersion;
|
||||
payload: FeedVersion;
|
||||
}
|
||||
|
||||
export interface BestBlockMessage extends MessageBase {
|
||||
action: typeof ACTIONS.BestBlock;
|
||||
payload: [BlockNumber, Timestamp, Maybe<Milliseconds>];
|
||||
}
|
||||
interface BestBlockMessage extends MessageBase {
|
||||
action: typeof ACTIONS.BestBlock;
|
||||
payload: [BlockNumber, Timestamp, Maybe<Milliseconds>];
|
||||
}
|
||||
|
||||
export interface BestFinalizedBlockMessage extends MessageBase {
|
||||
action: typeof ACTIONS.BestFinalized;
|
||||
payload: [BlockNumber, BlockHash];
|
||||
}
|
||||
interface BestFinalizedBlockMessage extends MessageBase {
|
||||
action: typeof ACTIONS.BestFinalized;
|
||||
payload: [BlockNumber, BlockHash];
|
||||
}
|
||||
|
||||
export interface AddedNodeMessage extends MessageBase {
|
||||
action: typeof ACTIONS.AddedNode;
|
||||
payload: [
|
||||
NodeId,
|
||||
NodeDetails,
|
||||
NodeStats,
|
||||
NodeIO,
|
||||
NodeHardware,
|
||||
BlockDetails,
|
||||
Maybe<NodeLocation>,
|
||||
Maybe<Timestamp>
|
||||
];
|
||||
}
|
||||
interface AddedNodeMessage extends MessageBase {
|
||||
action: typeof ACTIONS.AddedNode;
|
||||
payload: [
|
||||
NodeId,
|
||||
NodeDetails,
|
||||
NodeStats,
|
||||
NodeIO,
|
||||
NodeHardware,
|
||||
BlockDetails,
|
||||
Maybe<NodeLocation>,
|
||||
Maybe<Timestamp>
|
||||
];
|
||||
}
|
||||
|
||||
export interface RemovedNodeMessage extends MessageBase {
|
||||
action: typeof ACTIONS.RemovedNode;
|
||||
payload: NodeId;
|
||||
}
|
||||
interface RemovedNodeMessage extends MessageBase {
|
||||
action: typeof ACTIONS.RemovedNode;
|
||||
payload: NodeId;
|
||||
}
|
||||
|
||||
export interface LocatedNodeMessage extends MessageBase {
|
||||
action: typeof ACTIONS.LocatedNode;
|
||||
payload: [NodeId, Latitude, Longitude, City];
|
||||
}
|
||||
interface LocatedNodeMessage extends MessageBase {
|
||||
action: typeof ACTIONS.LocatedNode;
|
||||
payload: [NodeId, Latitude, Longitude, City];
|
||||
}
|
||||
|
||||
export interface ImportedBlockMessage extends MessageBase {
|
||||
action: typeof ACTIONS.ImportedBlock;
|
||||
payload: [NodeId, BlockDetails];
|
||||
}
|
||||
interface ImportedBlockMessage extends MessageBase {
|
||||
action: typeof ACTIONS.ImportedBlock;
|
||||
payload: [NodeId, BlockDetails];
|
||||
}
|
||||
|
||||
export interface FinalizedBlockMessage extends MessageBase {
|
||||
action: typeof ACTIONS.FinalizedBlock;
|
||||
payload: [NodeId, BlockNumber, BlockHash];
|
||||
}
|
||||
interface FinalizedBlockMessage extends MessageBase {
|
||||
action: typeof ACTIONS.FinalizedBlock;
|
||||
payload: [NodeId, BlockNumber, BlockHash];
|
||||
}
|
||||
|
||||
export interface NodeStatsMessage extends MessageBase {
|
||||
action: typeof ACTIONS.NodeStats;
|
||||
payload: [NodeId, NodeStats];
|
||||
}
|
||||
interface NodeStatsMessage extends MessageBase {
|
||||
action: typeof ACTIONS.NodeStats;
|
||||
payload: [NodeId, NodeStats];
|
||||
}
|
||||
|
||||
export interface NodeHardwareMessage extends MessageBase {
|
||||
action: typeof ACTIONS.NodeHardware;
|
||||
payload: [NodeId, NodeHardware];
|
||||
}
|
||||
interface NodeHardwareMessage extends MessageBase {
|
||||
action: typeof ACTIONS.NodeHardware;
|
||||
payload: [NodeId, NodeHardware];
|
||||
}
|
||||
|
||||
export interface NodeIOMessage extends MessageBase {
|
||||
action: typeof ACTIONS.NodeIO;
|
||||
payload: [NodeId, NodeIO];
|
||||
}
|
||||
interface NodeIOMessage extends MessageBase {
|
||||
action: typeof ACTIONS.NodeIO;
|
||||
payload: [NodeId, NodeIO];
|
||||
}
|
||||
|
||||
export interface TimeSyncMessage extends MessageBase {
|
||||
action: typeof ACTIONS.TimeSync;
|
||||
payload: Timestamp;
|
||||
}
|
||||
interface TimeSyncMessage extends MessageBase {
|
||||
action: typeof ACTIONS.TimeSync;
|
||||
payload: Timestamp;
|
||||
}
|
||||
|
||||
export interface AddedChainMessage extends MessageBase {
|
||||
action: typeof ACTIONS.AddedChain;
|
||||
payload: [ChainLabel, GenesisHash, NodeCount];
|
||||
}
|
||||
interface AddedChainMessage extends MessageBase {
|
||||
action: typeof ACTIONS.AddedChain;
|
||||
payload: [ChainLabel, GenesisHash, NodeCount];
|
||||
}
|
||||
|
||||
export interface RemovedChainMessage extends MessageBase {
|
||||
action: typeof ACTIONS.RemovedChain;
|
||||
payload: GenesisHash;
|
||||
}
|
||||
interface RemovedChainMessage extends MessageBase {
|
||||
action: typeof ACTIONS.RemovedChain;
|
||||
payload: GenesisHash;
|
||||
}
|
||||
|
||||
export interface SubscribedToMessage extends MessageBase {
|
||||
action: typeof ACTIONS.SubscribedTo;
|
||||
payload: GenesisHash;
|
||||
}
|
||||
interface SubscribedToMessage extends MessageBase {
|
||||
action: typeof ACTIONS.SubscribedTo;
|
||||
payload: GenesisHash;
|
||||
}
|
||||
|
||||
export interface UnsubscribedFromMessage extends MessageBase {
|
||||
action: typeof ACTIONS.UnsubscribedFrom;
|
||||
payload: GenesisHash;
|
||||
}
|
||||
interface UnsubscribedFromMessage extends MessageBase {
|
||||
action: typeof ACTIONS.UnsubscribedFrom;
|
||||
payload: GenesisHash;
|
||||
}
|
||||
|
||||
export interface PongMessage extends MessageBase {
|
||||
action: typeof ACTIONS.Pong;
|
||||
payload: string; // just echo whatever `ping` sent
|
||||
}
|
||||
interface PongMessage extends MessageBase {
|
||||
action: typeof ACTIONS.Pong;
|
||||
payload: string; // just echo whatever `ping` sent
|
||||
}
|
||||
|
||||
export interface AfgFinalizedMessage extends MessageBase {
|
||||
action: typeof ACTIONS.AfgFinalized;
|
||||
payload: [Address, BlockNumber, BlockHash];
|
||||
}
|
||||
interface AfgFinalizedMessage extends MessageBase {
|
||||
action: typeof ACTIONS.AfgFinalized;
|
||||
payload: [Address, BlockNumber, BlockHash];
|
||||
}
|
||||
|
||||
export interface AfgAuthoritySet extends MessageBase {
|
||||
action: typeof ACTIONS.AfgAuthoritySet;
|
||||
payload: AuthoritySetInfo;
|
||||
}
|
||||
interface AfgAuthoritySet extends MessageBase {
|
||||
action: typeof ACTIONS.AfgAuthoritySet;
|
||||
payload: AuthoritySetInfo;
|
||||
}
|
||||
|
||||
export interface AfgReceivedPrecommit extends MessageBase {
|
||||
action: typeof ACTIONS.AfgReceivedPrecommit;
|
||||
payload: [Address, BlockNumber, BlockHash, Address];
|
||||
}
|
||||
interface AfgReceivedPrecommit extends MessageBase {
|
||||
action: typeof ACTIONS.AfgReceivedPrecommit;
|
||||
payload: [Address, BlockNumber, BlockHash, Address];
|
||||
}
|
||||
|
||||
export interface AfgReceivedPrevote extends MessageBase {
|
||||
action: typeof ACTIONS.AfgReceivedPrevote;
|
||||
payload: [Address, BlockNumber, BlockHash, Address];
|
||||
}
|
||||
interface AfgReceivedPrevote extends MessageBase {
|
||||
action: typeof ACTIONS.AfgReceivedPrevote;
|
||||
payload: [Address, BlockNumber, BlockHash, Address];
|
||||
}
|
||||
|
||||
export interface StaleNodeMessage extends MessageBase {
|
||||
action: typeof ACTIONS.StaleNode;
|
||||
payload: NodeId;
|
||||
}
|
||||
interface StaleNodeMessage extends MessageBase {
|
||||
action: typeof ACTIONS.StaleNode;
|
||||
payload: NodeId;
|
||||
}
|
||||
|
||||
export interface ChainStatsUpdate extends MessageBase {
|
||||
action: typeof ACTIONS.ChainStatsUpdate;
|
||||
payload: ChainStats;
|
||||
}
|
||||
interface ChainStatsUpdate extends MessageBase {
|
||||
action: typeof ACTIONS.ChainStatsUpdate;
|
||||
payload: ChainStats;
|
||||
}
|
||||
|
||||
export type Message =
|
||||
| Variants.FeedVersionMessage
|
||||
| Variants.BestBlockMessage
|
||||
| Variants.BestFinalizedBlockMessage
|
||||
| Variants.AddedNodeMessage
|
||||
| Variants.RemovedNodeMessage
|
||||
| Variants.LocatedNodeMessage
|
||||
| Variants.ImportedBlockMessage
|
||||
| Variants.FinalizedBlockMessage
|
||||
| Variants.NodeStatsMessage
|
||||
| Variants.NodeHardwareMessage
|
||||
| Variants.TimeSyncMessage
|
||||
| Variants.AddedChainMessage
|
||||
| Variants.RemovedChainMessage
|
||||
| Variants.SubscribedToMessage
|
||||
| Variants.UnsubscribedFromMessage
|
||||
| Variants.AfgFinalizedMessage
|
||||
| Variants.AfgReceivedPrevote
|
||||
| Variants.AfgReceivedPrecommit
|
||||
| Variants.AfgAuthoritySet
|
||||
| Variants.StaleNodeMessage
|
||||
| Variants.PongMessage
|
||||
| Variants.NodeIOMessage
|
||||
| Variants.ChainStatsUpdate;
|
||||
| FeedVersionMessage
|
||||
| BestBlockMessage
|
||||
| BestFinalizedBlockMessage
|
||||
| AddedNodeMessage
|
||||
| RemovedNodeMessage
|
||||
| LocatedNodeMessage
|
||||
| ImportedBlockMessage
|
||||
| FinalizedBlockMessage
|
||||
| NodeStatsMessage
|
||||
| NodeHardwareMessage
|
||||
| TimeSyncMessage
|
||||
| AddedChainMessage
|
||||
| RemovedChainMessage
|
||||
| SubscribedToMessage
|
||||
| UnsubscribedFromMessage
|
||||
| AfgFinalizedMessage
|
||||
| AfgReceivedPrevote
|
||||
| AfgReceivedPrecommit
|
||||
| AfgAuthoritySet
|
||||
| StaleNodeMessage
|
||||
| PongMessage
|
||||
| NodeIOMessage
|
||||
| ChainStatsUpdate;
|
||||
|
||||
/**
|
||||
* Data type to be sent to the feed. Passing through strings means we can only serialize once,
|
||||
* no matter how many feed clients are listening in.
|
||||
*/
|
||||
export interface SquashedMessages extends Array<Action | Payload> {}
|
||||
export type SquashedMessages = Array<Action | Payload>;
|
||||
export type Data = Stringified<SquashedMessages>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,15 +45,13 @@ export type Maybe<T> = T | null | undefined;
|
||||
* Asynchronous sleep
|
||||
*/
|
||||
export function sleep(time: Milliseconds): Promise<void> {
|
||||
return new Promise<void>((resolve, _reject) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(() => resolve(), time);
|
||||
});
|
||||
}
|
||||
|
||||
export const timestamp = Date.now as () => Timestamp;
|
||||
|
||||
export function noop() {}
|
||||
|
||||
/**
|
||||
* Keep track of last N numbers pushed onto internal stack.
|
||||
* Provides means to get an average of said numbers.
|
||||
|
||||
@@ -24,6 +24,7 @@ export type Id<T> = Opaque<number, T>;
|
||||
/**
|
||||
* Higher order function producing new auto-incremented `Id`s.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function idGenerator<I extends Id<any>>(): () => I {
|
||||
let current = 0;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export abstract class Stringified<T> {
|
||||
public __PHANTOM__: T;
|
||||
}
|
||||
|
||||
export const parse = (JSON.parse as any) as <T>(val: Stringified<T>) => T;
|
||||
export const stringify = (JSON.stringify as any) as <T>(
|
||||
val: T
|
||||
) => Stringified<T>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const parse = JSON.parse as any as <T>(val: Stringified<T>) => T;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const stringify = JSON.stringify as any as <T>(val: T) => Stringified<T>;
|
||||
|
||||
@@ -18,15 +18,13 @@ import * as React from 'react';
|
||||
import './Tile.css';
|
||||
import { timestamp, Types } from '../common';
|
||||
|
||||
export namespace Ago {
|
||||
export interface Props {
|
||||
when: Types.Timestamp;
|
||||
justTime?: boolean;
|
||||
}
|
||||
interface AgoProps {
|
||||
when: Types.Timestamp;
|
||||
justTime?: boolean;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
now: Types.Timestamp;
|
||||
}
|
||||
interface AgoState {
|
||||
now: Types.Timestamp;
|
||||
}
|
||||
|
||||
const tickers = new Map<Ago, (ts: Types.Timestamp) => void>();
|
||||
@@ -43,20 +41,14 @@ function tick() {
|
||||
|
||||
tick();
|
||||
|
||||
export namespace Ago {
|
||||
export interface State {
|
||||
now: Types.Timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
export class Ago extends React.Component<Ago.Props, Ago.State> {
|
||||
export class Ago extends React.Component<AgoProps, AgoState> {
|
||||
public static timeDiff = 0 as Types.Milliseconds;
|
||||
|
||||
public state: Ago.State;
|
||||
public state: AgoState;
|
||||
|
||||
private agoStr: string;
|
||||
|
||||
constructor(props: Ago.Props) {
|
||||
constructor(props: AgoProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -65,7 +57,7 @@ export class Ago extends React.Component<Ago.Props, Ago.State> {
|
||||
this.agoStr = this.stringify(props.when, this.state.now);
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: Ago.Props, nextState: Ago.State) {
|
||||
public shouldComponentUpdate(nextProps: AgoProps, nextState: AgoState) {
|
||||
const nextAgoStr = this.stringify(nextProps.when, nextState.now);
|
||||
|
||||
if (this.agoStr !== nextAgoStr) {
|
||||
|
||||
@@ -45,11 +45,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5em;
|
||||
border-bottom: 1px solid rgba(0,0,0,0.1);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.AllChains-controls input {
|
||||
border: 1px solid rgba(0,0,0,0.5);
|
||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||
border-radius: 4px;
|
||||
padding: 0.5em;
|
||||
flex-grow: 1;
|
||||
@@ -81,7 +81,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
.AllChains-chain {
|
||||
padding: 10px 10px;
|
||||
background: rgb(220,220,220);
|
||||
background: rgb(220, 220, 220);
|
||||
color: black;
|
||||
display: inline-flex;
|
||||
margin-right: 0.5em;
|
||||
|
||||
@@ -21,15 +21,13 @@ import { ChainData } from '../state';
|
||||
|
||||
import './AllChains.css';
|
||||
|
||||
export namespace AllChains {
|
||||
export interface Props {
|
||||
chains: ChainData[];
|
||||
subscribed: Maybe<Types.GenesisHash>;
|
||||
connection: Promise<Connection>;
|
||||
}
|
||||
interface AllChainsProps {
|
||||
chains: ChainData[];
|
||||
subscribed: Maybe<Types.GenesisHash>;
|
||||
connection: Promise<Connection>;
|
||||
}
|
||||
|
||||
export function AllChains(props: AllChains.Props) {
|
||||
export function AllChains(props: AllChainsProps) {
|
||||
const { chains, subscribed, connection } = props;
|
||||
const [filterText, setFilterText] = React.useState('');
|
||||
const [sortBy, setSortBy] = React.useState(SortBy.NumberOfNodes);
|
||||
|
||||
@@ -17,7 +17,11 @@
|
||||
import * as React from 'react';
|
||||
import { Connection } from '../../Connection';
|
||||
import { Types, Maybe } from '../../common';
|
||||
import { State as AppState, Update as AppUpdate } from '../../state';
|
||||
import {
|
||||
State as AppState,
|
||||
Update as AppUpdate,
|
||||
StateSettings,
|
||||
} from '../../state';
|
||||
import { getHashData } from '../../utils';
|
||||
import { Header } from './';
|
||||
import { List, Map, Settings, Stats } from '../';
|
||||
@@ -25,28 +29,26 @@ import { Persistent, PersistentObject, PersistentSet } from '../../persist';
|
||||
|
||||
import './Chain.css';
|
||||
|
||||
export namespace Chain {
|
||||
export type Display = 'list' | 'map' | 'settings' | 'consensus' | 'stats';
|
||||
export type ChainDisplay = 'list' | 'map' | 'settings' | 'consensus' | 'stats';
|
||||
|
||||
export interface Props {
|
||||
appState: Readonly<AppState>;
|
||||
appUpdate: AppUpdate;
|
||||
connection: Promise<Connection>;
|
||||
settings: PersistentObject<AppState.Settings>;
|
||||
pins: PersistentSet<Types.NodeName>;
|
||||
sortBy: Persistent<Maybe<number>>;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
display: Display;
|
||||
}
|
||||
interface ChainProps {
|
||||
appState: Readonly<AppState>;
|
||||
appUpdate: AppUpdate;
|
||||
connection: Promise<Connection>;
|
||||
settings: PersistentObject<StateSettings>;
|
||||
pins: PersistentSet<Types.NodeName>;
|
||||
sortBy: Persistent<Maybe<number>>;
|
||||
}
|
||||
|
||||
export class Chain extends React.Component<Chain.Props, Chain.State> {
|
||||
constructor(props: Chain.Props) {
|
||||
interface ChainState {
|
||||
display: ChainDisplay;
|
||||
}
|
||||
|
||||
export class Chain extends React.Component<ChainProps, ChainState> {
|
||||
constructor(props: ChainProps) {
|
||||
super(props);
|
||||
|
||||
let display: Chain.Display = 'list';
|
||||
let display: ChainDisplay = 'list';
|
||||
|
||||
switch (getHashData().tab) {
|
||||
case 'map':
|
||||
@@ -91,7 +93,7 @@ export class Chain extends React.Component<Chain.Props, Chain.State> {
|
||||
return <Settings settings={this.props.settings} />;
|
||||
}
|
||||
|
||||
const { appState, appUpdate, connection, pins, sortBy } = this.props;
|
||||
const { appState, appUpdate, pins, sortBy } = this.props;
|
||||
|
||||
if (display === 'list') {
|
||||
return (
|
||||
@@ -115,7 +117,7 @@ export class Chain extends React.Component<Chain.Props, Chain.State> {
|
||||
throw new Error('invalid `display`: ${display}');
|
||||
}
|
||||
|
||||
private setDisplay = (display: Chain.Display) => {
|
||||
private setDisplay = (display: ChainDisplay) => {
|
||||
this.setState({ display });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import * as React from 'react';
|
||||
import { Types, Maybe } from '../../common';
|
||||
import { formatNumber, secondsWithPrecision } from '../../utils';
|
||||
import { Tab, Chain } from './';
|
||||
import { Tab, ChainDisplay } from './';
|
||||
import { Tile, Ago } from '../';
|
||||
|
||||
import blockIcon from '../../icons/cube.svg';
|
||||
@@ -27,24 +27,21 @@ import lastTimeIcon from '../../icons/watch.svg';
|
||||
import listIcon from '../../icons/list-alt-regular.svg';
|
||||
import worldIcon from '../../icons/location.svg';
|
||||
import settingsIcon from '../../icons/settings.svg';
|
||||
import consensusIcon from '../../icons/cube-alt.svg';
|
||||
import statsIcon from '../../icons/graph.svg';
|
||||
|
||||
import './Header.css';
|
||||
|
||||
export namespace Header {
|
||||
export interface Props {
|
||||
best: Types.BlockNumber;
|
||||
finalized: Types.BlockNumber;
|
||||
blockTimestamp: Types.Timestamp;
|
||||
blockAverage: Maybe<Types.Milliseconds>;
|
||||
currentTab: Chain.Display;
|
||||
setDisplay: (display: Chain.Display) => void;
|
||||
}
|
||||
interface HeaderProps {
|
||||
best: Types.BlockNumber;
|
||||
finalized: Types.BlockNumber;
|
||||
blockTimestamp: Types.Timestamp;
|
||||
blockAverage: Maybe<Types.Milliseconds>;
|
||||
currentTab: ChainDisplay;
|
||||
setDisplay: (display: ChainDisplay) => void;
|
||||
}
|
||||
|
||||
export class Header extends React.Component<Header.Props, {}> {
|
||||
public shouldComponentUpdate(nextProps: Header.Props) {
|
||||
export class Header extends React.Component<HeaderProps> {
|
||||
public shouldComponentUpdate(nextProps: HeaderProps) {
|
||||
return (
|
||||
this.props.best !== nextProps.best ||
|
||||
this.props.finalized !== nextProps.finalized ||
|
||||
|
||||
@@ -28,7 +28,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
color: #555;
|
||||
cursor: pointer;
|
||||
border-radius: 40px;
|
||||
box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.Chain-Tab:hover {
|
||||
|
||||
@@ -15,24 +15,22 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Chain } from './';
|
||||
import { ChainDisplay } from './';
|
||||
import { Icon } from '../';
|
||||
import { setHashData } from '../../utils';
|
||||
|
||||
import './Tab.css';
|
||||
|
||||
export namespace Tab {
|
||||
export interface Props {
|
||||
label: string;
|
||||
icon: string;
|
||||
display: Chain.Display;
|
||||
current: string;
|
||||
tab: string;
|
||||
setDisplay: (display: Chain.Display) => void;
|
||||
}
|
||||
interface TabProps {
|
||||
label: string;
|
||||
icon: string;
|
||||
display: ChainDisplay;
|
||||
current: string;
|
||||
tab: string;
|
||||
setDisplay: (display: ChainDisplay) => void;
|
||||
}
|
||||
|
||||
export class Tab extends React.Component<Tab.Props, {}> {
|
||||
export class Tab extends React.Component<TabProps> {
|
||||
public render() {
|
||||
const { label, icon, display, current } = this.props;
|
||||
const highlight = display === current;
|
||||
|
||||
@@ -26,11 +26,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
}
|
||||
|
||||
.Chains-extra-subscribed-chain {
|
||||
position: absolute;
|
||||
position: absolute;
|
||||
right: 80px;
|
||||
top: 0px;
|
||||
background: #e6007a;
|
||||
background: linear-gradient(90deg, rgba(0,0,0,0) 0px, #e6007a 30px, #e6007a 100%);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(0, 0, 0, 0) 0px,
|
||||
#e6007a 30px,
|
||||
#e6007a 100%
|
||||
);
|
||||
z-index: 1;
|
||||
padding-left: 34px;
|
||||
}
|
||||
|
||||
@@ -25,13 +25,11 @@ import githubIcon from '../icons/mark-github.svg';
|
||||
import listIcon from '../icons/kebab-horizontal.svg';
|
||||
import './Chains.css';
|
||||
|
||||
export namespace Chains {
|
||||
export interface Props {
|
||||
chains: ChainData[];
|
||||
subscribedHash: Maybe<Types.GenesisHash>;
|
||||
subscribedData: Maybe<ChainData>;
|
||||
connection: Promise<Connection>;
|
||||
}
|
||||
interface ChainsProps {
|
||||
chains: ChainData[];
|
||||
subscribedHash: Maybe<Types.GenesisHash>;
|
||||
subscribedData: Maybe<ChainData>;
|
||||
connection: Promise<Connection>;
|
||||
}
|
||||
|
||||
// Average tab width in pixels
|
||||
@@ -39,12 +37,12 @@ const AVERAGE_TAB_WIDTH = 160;
|
||||
// Milliseconds, sets the minimum time between the renders
|
||||
const RENDER_THROTTLE = 1000;
|
||||
|
||||
export class Chains extends React.Component<Chains.Props, {}> {
|
||||
export class Chains extends React.Component<ChainsProps> {
|
||||
private lastRender = performance.now();
|
||||
private clicked: Maybe<Types.GenesisHash>;
|
||||
private subscribedChainInView = false;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Chains.Props) {
|
||||
public shouldComponentUpdate(nextProps: ChainsProps) {
|
||||
if (nextProps.subscribedHash !== this.clicked) {
|
||||
this.clicked = nextProps.subscribedHash;
|
||||
}
|
||||
@@ -68,7 +66,7 @@ export class Chains extends React.Component<Chains.Props, {}> {
|
||||
|
||||
const allChainsHref = subscribedHash
|
||||
? `#all-chains/${subscribedHash}`
|
||||
: `#all-chains`;
|
||||
: '#all-chains';
|
||||
|
||||
const subscribedChain =
|
||||
subscribedData && !this.subscribedChainInView ? (
|
||||
@@ -93,6 +91,7 @@ export class Chains extends React.Component<Chains.Props, {}> {
|
||||
href="https://github.com/paritytech/substrate-telemetry"
|
||||
target="_blank"
|
||||
title="Fork Me!"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Icon src={githubIcon} />
|
||||
</a>
|
||||
|
||||
@@ -23,19 +23,17 @@ import searchIcon from '../icons/search.svg';
|
||||
|
||||
import './Filter.css';
|
||||
|
||||
export namespace Filter {
|
||||
export interface Props {
|
||||
onChange: (value: Maybe<(node: Node) => boolean>) => void;
|
||||
}
|
||||
interface FilterProps {
|
||||
onChange: (value: Maybe<(node: Node) => boolean>) => void;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
value: string;
|
||||
}
|
||||
interface FilterState {
|
||||
value: string;
|
||||
}
|
||||
|
||||
const ESCAPE_KEY = 27;
|
||||
|
||||
export class Filter extends React.Component<Filter.Props, {}> {
|
||||
export class Filter extends React.Component<FilterProps, FilterState> {
|
||||
public state = {
|
||||
value: '',
|
||||
};
|
||||
@@ -51,8 +49,8 @@ export class Filter extends React.Component<Filter.Props, {}> {
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(
|
||||
nextProps: Filter.Props,
|
||||
nextState: Filter.State
|
||||
nextProps: FilterProps,
|
||||
nextState: FilterState
|
||||
): boolean {
|
||||
return (
|
||||
this.props.onChange !== nextProps.onChange ||
|
||||
@@ -110,6 +108,7 @@ export class Filter extends React.Component<Filter.Props, {}> {
|
||||
return;
|
||||
}
|
||||
// Ignore events dispatched to other elements that want to use it
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (['INPUT', 'TEXTAREA'].includes((event.target as any)?.tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.Icon svg, .Icon-symbol-root symbol {
|
||||
.Icon svg,
|
||||
.Icon-symbol-root symbol {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
@@ -18,12 +18,10 @@ import * as React from 'react';
|
||||
import './Icon.css';
|
||||
import { getSVGShadowRoot, W3SVG } from '../utils';
|
||||
|
||||
export namespace Icon {
|
||||
export interface Props {
|
||||
src: string;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
interface IconProps {
|
||||
src: string;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const symbols = new Map<string, string>();
|
||||
@@ -67,10 +65,10 @@ function renderShadowIcon(src: string): string {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
export class Icon extends React.Component<Icon.Props, {}> {
|
||||
public props: Icon.Props;
|
||||
export class Icon extends React.Component<IconProps> {
|
||||
public props: IconProps;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Icon.Props) {
|
||||
public shouldComponentUpdate(nextProps: IconProps) {
|
||||
return (
|
||||
this.props.src !== nextProps.src ||
|
||||
this.props.className !== nextProps.className
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Maybe } from '../../../common';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { Truncate, Tooltip } from '../../';
|
||||
import { Truncate, Tooltip, TooltipCopyCallback } from '../../';
|
||||
import icon from '../../../icons/file-binary.svg';
|
||||
|
||||
export class BlockHashColumn extends React.Component<Column.Props, {}> {
|
||||
export class BlockHashColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Block Hash';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 154;
|
||||
@@ -29,9 +29,9 @@ export class BlockHashColumn extends React.Component<Column.Props, {}> {
|
||||
public static readonly sortBy = ({ hash }: Node) => hash || '';
|
||||
|
||||
private data: Maybe<string>;
|
||||
private copy: Maybe<Tooltip.CopyCallback>;
|
||||
private copy: Maybe<TooltipCopyCallback>;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
return this.data !== nextProps.node.hash;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export class BlockHashColumn extends React.Component<Column.Props, {}> {
|
||||
);
|
||||
}
|
||||
|
||||
private onCopy = (copy: Tooltip.CopyCallback) => {
|
||||
private onCopy = (copy: TooltipCopyCallback) => {
|
||||
this.copy = copy;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { formatNumber } from '../../../utils';
|
||||
import icon from '../../../icons/cube.svg';
|
||||
|
||||
export class BlockNumberColumn extends React.Component<Column.Props, {}> {
|
||||
export class BlockNumberColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Block';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 88;
|
||||
@@ -29,7 +29,7 @@ export class BlockNumberColumn extends React.Component<Column.Props, {}> {
|
||||
|
||||
private data = 0;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
return this.data !== nextProps.node.height;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Maybe } from '../../../common';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { milliOrSecond } from '../../../utils';
|
||||
import icon from '../../../icons/dashboard.svg';
|
||||
|
||||
export class BlockPropagationColumn extends React.Component<Column.Props, {}> {
|
||||
export class BlockPropagationColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Block Propagation Time';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 58;
|
||||
@@ -31,7 +31,7 @@ export class BlockPropagationColumn extends React.Component<Column.Props, {}> {
|
||||
|
||||
private data: Maybe<number>;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
return this.data !== nextProps.node.propagationTime;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { secondsWithPrecision } from '../../../utils';
|
||||
import icon from '../../../icons/history.svg';
|
||||
|
||||
export class BlockTimeColumn extends React.Component<Column.Props, {}> {
|
||||
export class BlockTimeColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Block Time';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 80;
|
||||
@@ -30,7 +30,7 @@ export class BlockTimeColumn extends React.Component<Column.Props, {}> {
|
||||
|
||||
private data = 0;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
return this.data !== nextProps.node.blockTime;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,4 +58,3 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
.Column--a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Types, Maybe, timestamp } from '../../../common';
|
||||
import { Node } from '../../../state';
|
||||
|
||||
@@ -61,41 +60,39 @@ export type Column =
|
||||
| typeof LastBlockColumn
|
||||
| typeof UptimeColumn;
|
||||
|
||||
export namespace Column {
|
||||
export interface Props {
|
||||
node: Node;
|
||||
export interface ColumnProps {
|
||||
node: Node;
|
||||
}
|
||||
|
||||
export function formatBytes(
|
||||
bytes: number,
|
||||
stamp: Maybe<Types.Timestamp>
|
||||
): string {
|
||||
const ago = stamp ? ` (${formatStamp(stamp)})` : '';
|
||||
|
||||
if (bytes >= 1024 * 1024 * 1024) {
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB${ago}`;
|
||||
} else if (bytes >= 1024 * 1024) {
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB${ago}`;
|
||||
} else if (bytes >= 1000) {
|
||||
return `${(bytes / 1024).toFixed(1)} kB${ago}`;
|
||||
} else {
|
||||
return `${bytes} B${ago}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatBytes(
|
||||
bytes: number,
|
||||
stamp: Maybe<Types.Timestamp>
|
||||
): string {
|
||||
const ago = stamp ? ` (${formatStamp(stamp)})` : '';
|
||||
export function formatBandwidth(
|
||||
bps: number,
|
||||
stamp: Maybe<Types.Timestamp>
|
||||
): string {
|
||||
const ago = stamp ? ` (${formatStamp(stamp)})` : '';
|
||||
|
||||
if (bytes >= 1024 * 1024 * 1024) {
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB${ago}`;
|
||||
} else if (bytes >= 1024 * 1024) {
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB${ago}`;
|
||||
} else if (bytes >= 1000) {
|
||||
return `${(bytes / 1024).toFixed(1)} kB${ago}`;
|
||||
} else {
|
||||
return `${bytes} B${ago}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatBandwidth(
|
||||
bps: number,
|
||||
stamp: Maybe<Types.Timestamp>
|
||||
): string {
|
||||
const ago = stamp ? ` (${formatStamp(stamp)})` : '';
|
||||
|
||||
if (bps >= 1024 * 1024) {
|
||||
return `${(bps / (1024 * 1024)).toFixed(1)} MB/s${ago}`;
|
||||
} else if (bps >= 1000) {
|
||||
return `${(bps / 1024).toFixed(1)} kB/s${ago}`;
|
||||
} else {
|
||||
return `${bps | 0} B/s${ago}`;
|
||||
}
|
||||
if (bps >= 1024 * 1024) {
|
||||
return `${(bps / (1024 * 1024)).toFixed(1)} MB/s${ago}`;
|
||||
} else if (bps >= 1000) {
|
||||
return `${(bps / 1024).toFixed(1)} kB/s${ago}`;
|
||||
} else {
|
||||
return `${bps | 0} B/s${ago}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Types, Maybe, timestamp } from '../../../common';
|
||||
import { Column, BANDWIDTH_SCALE } from './';
|
||||
import { ColumnProps, formatBandwidth, BANDWIDTH_SCALE } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { Sparkline } from '../../';
|
||||
import icon from '../../../icons/cloud-download.svg';
|
||||
|
||||
export class DownloadColumn extends React.Component<Column.Props, {}> {
|
||||
export class DownloadColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Download Bandwidth';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 40;
|
||||
@@ -31,7 +30,7 @@ export class DownloadColumn extends React.Component<Column.Props, {}> {
|
||||
|
||||
private data: Array<number> = [];
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
// Diffing by ref, as data is an immutable array
|
||||
return this.data !== nextProps.node.download;
|
||||
}
|
||||
@@ -51,7 +50,7 @@ export class DownloadColumn extends React.Component<Column.Props, {}> {
|
||||
width={44}
|
||||
height={16}
|
||||
stroke={1}
|
||||
format={Column.formatBandwidth}
|
||||
format={formatBandwidth}
|
||||
values={download}
|
||||
stamps={chartstamps}
|
||||
minScale={BANDWIDTH_SCALE}
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { formatNumber } from '../../../utils';
|
||||
import icon from '../../../icons/cube-alt.svg';
|
||||
|
||||
export class FinalizedBlockColumn extends React.Component<Column.Props, {}> {
|
||||
export class FinalizedBlockColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Finalized Block';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 88;
|
||||
@@ -29,7 +29,7 @@ export class FinalizedBlockColumn extends React.Component<Column.Props, {}> {
|
||||
|
||||
private data = 0;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
return this.data !== nextProps.node.finalized;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Maybe } from '../../../common';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { Truncate, Tooltip } from '../../';
|
||||
import { Truncate, Tooltip, TooltipCopyCallback } from '../../';
|
||||
import icon from '../../../icons/file-binary.svg';
|
||||
|
||||
export class FinalizedHashColumn extends React.Component<Column.Props, {}> {
|
||||
export class FinalizedHashColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Finalized Block Hash';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 154;
|
||||
@@ -30,9 +30,9 @@ export class FinalizedHashColumn extends React.Component<Column.Props, {}> {
|
||||
finalizedHash || '';
|
||||
|
||||
private data: Maybe<string>;
|
||||
private copy: Maybe<Tooltip.CopyCallback>;
|
||||
private copy: Maybe<TooltipCopyCallback>;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
return this.data !== nextProps.node.finalizedHash;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export class FinalizedHashColumn extends React.Component<Column.Props, {}> {
|
||||
);
|
||||
}
|
||||
|
||||
private onCopy = (copy: Tooltip.CopyCallback) => {
|
||||
private onCopy = (copy: TooltipCopyCallback) => {
|
||||
this.copy = copy;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { Tooltip, Icon } from '../../';
|
||||
import icon from '../../../icons/terminal.svg';
|
||||
@@ -69,7 +69,7 @@ const ICONS = {
|
||||
};
|
||||
const SEMVER_PATTERN = /^\d+\.\d+\.\d+/;
|
||||
|
||||
export class ImplementationColumn extends React.Component<Column.Props, {}> {
|
||||
export class ImplementationColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Implementation';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 90;
|
||||
@@ -80,7 +80,7 @@ export class ImplementationColumn extends React.Component<Column.Props, {}> {
|
||||
private implementation: string;
|
||||
private version: string;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
if (this.props.node === nextProps.node) {
|
||||
// Implementation can't change unless we got a new node
|
||||
return false;
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { Ago } from '../../';
|
||||
import icon from '../../../icons/watch.svg';
|
||||
|
||||
export class LastBlockColumn extends React.Component<Column.Props, {}> {
|
||||
export class LastBlockColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Last Block Time';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 100;
|
||||
@@ -30,7 +30,7 @@ export class LastBlockColumn extends React.Component<Column.Props, {}> {
|
||||
|
||||
private data = 0;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
return this.data !== nextProps.node.blockTimestamp;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Maybe } from '../../../common';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { Truncate, Tooltip } from '../../';
|
||||
import icon from '../../../icons/location.svg';
|
||||
|
||||
export class LocationColumn extends React.Component<Column.Props, {}> {
|
||||
export class LocationColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Location';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 140;
|
||||
@@ -30,7 +30,7 @@ export class LocationColumn extends React.Component<Column.Props, {}> {
|
||||
|
||||
private data: Maybe<string>;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
return this.data !== nextProps.node.city;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,19 +15,19 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { Truncate, Tooltip } from '../../';
|
||||
import icon from '../../../icons/server.svg';
|
||||
|
||||
export class NameColumn extends React.Component<Column.Props, {}> {
|
||||
export class NameColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Node';
|
||||
public static readonly icon = icon;
|
||||
public static readonly setting = null;
|
||||
public static readonly width = null;
|
||||
public static readonly sortBy = ({ sortableName }: Node) => sortableName;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
// Node name only changes when the node does
|
||||
return this.props.node !== nextProps.node;
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Maybe } from '../../../common';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { Truncate } from '../../';
|
||||
import { Tooltip } from '../../';
|
||||
import icon from '../../../icons/fingerprint.svg';
|
||||
|
||||
export class NetworkIdColumn extends React.Component<Column.Props, {}> {
|
||||
export class NetworkIdColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Network ID';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 90;
|
||||
@@ -31,7 +31,7 @@ export class NetworkIdColumn extends React.Component<Column.Props, {}> {
|
||||
|
||||
private data: Maybe<string>;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
return this.data !== nextProps.node.networkId;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import icon from '../../../icons/broadcast.svg';
|
||||
|
||||
export class PeersColumn extends React.Component<Column.Props, {}> {
|
||||
export class PeersColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Peer Count';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 26;
|
||||
@@ -28,7 +28,7 @@ export class PeersColumn extends React.Component<Column.Props, {}> {
|
||||
|
||||
private data = 0;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
return this.data !== nextProps.node.peers;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Types, Maybe, timestamp } from '../../../common';
|
||||
import { Column, BANDWIDTH_SCALE } from './';
|
||||
import { ColumnProps, formatBytes, BANDWIDTH_SCALE } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { Sparkline } from '../../';
|
||||
import icon from '../../../icons/git-branch.svg';
|
||||
|
||||
export class StateCacheColumn extends React.Component<Column.Props, {}> {
|
||||
export class StateCacheColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'State Cache Size';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 40;
|
||||
@@ -31,7 +30,7 @@ export class StateCacheColumn extends React.Component<Column.Props, {}> {
|
||||
|
||||
private data: Array<number> = [];
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
// Diffing by ref, as data is an immutable array
|
||||
return this.data !== nextProps.node.stateCacheSize;
|
||||
}
|
||||
@@ -51,7 +50,7 @@ export class StateCacheColumn extends React.Component<Column.Props, {}> {
|
||||
width={44}
|
||||
height={16}
|
||||
stroke={1}
|
||||
format={Column.formatBytes}
|
||||
format={formatBytes}
|
||||
values={stateCacheSize}
|
||||
stamps={chartstamps}
|
||||
minScale={BANDWIDTH_SCALE}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import icon from '../../../icons/inbox.svg';
|
||||
|
||||
export class TxsColumn extends React.Component<Column.Props, {}> {
|
||||
export class TxsColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Transactions in Queue';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 26;
|
||||
@@ -28,7 +28,7 @@ export class TxsColumn extends React.Component<Column.Props, {}> {
|
||||
|
||||
private data = 0;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
return this.data !== nextProps.node.txs;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Types, Maybe, timestamp } from '../../../common';
|
||||
import { Column, BANDWIDTH_SCALE } from './';
|
||||
import { ColumnProps, formatBandwidth, BANDWIDTH_SCALE } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { Sparkline } from '../../';
|
||||
import icon from '../../../icons/cloud-upload.svg';
|
||||
|
||||
export class UploadColumn extends React.Component<Column.Props, {}> {
|
||||
export class UploadColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Upload Bandwidth';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 40;
|
||||
@@ -31,7 +30,7 @@ export class UploadColumn extends React.Component<Column.Props, {}> {
|
||||
|
||||
private data: Array<number> = [];
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
// Diffing by ref, as data is an immutable array
|
||||
return this.data !== nextProps.node.upload;
|
||||
}
|
||||
@@ -51,7 +50,7 @@ export class UploadColumn extends React.Component<Column.Props, {}> {
|
||||
width={44}
|
||||
height={16}
|
||||
stroke={1}
|
||||
format={Column.formatBandwidth}
|
||||
format={formatBandwidth}
|
||||
values={upload}
|
||||
stamps={chartstamps}
|
||||
minScale={BANDWIDTH_SCALE}
|
||||
|
||||
@@ -15,19 +15,19 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { Ago } from '../../';
|
||||
import icon from '../../../icons/pulse.svg';
|
||||
|
||||
export class UptimeColumn extends React.Component<Column.Props, {}> {
|
||||
export class UptimeColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Node Uptime';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 58;
|
||||
public static readonly setting = 'uptime';
|
||||
public static readonly sortBy = ({ startupTime }: Node) => startupTime || 0;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
// Uptime only changes when the node does
|
||||
return this.props.node !== nextProps.node;
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Maybe } from '../../../common';
|
||||
import { Column } from './';
|
||||
import { ColumnProps } from './';
|
||||
import { Node } from '../../../state';
|
||||
import { Tooltip, PolkadotIcon } from '../../';
|
||||
import { Tooltip, PolkadotIcon, TooltipCopyCallback } from '../../';
|
||||
import icon from '../../../icons/shield.svg';
|
||||
|
||||
export class ValidatorColumn extends React.Component<Column.Props, {}> {
|
||||
export class ValidatorColumn extends React.Component<ColumnProps> {
|
||||
public static readonly label = 'Validator';
|
||||
public static readonly icon = icon;
|
||||
public static readonly width = 16;
|
||||
@@ -29,9 +29,9 @@ export class ValidatorColumn extends React.Component<Column.Props, {}> {
|
||||
public static readonly sortBy = ({ validator }: Node) => validator || '';
|
||||
|
||||
private data: Maybe<string>;
|
||||
private copy: Maybe<Tooltip.CopyCallback>;
|
||||
private copy: Maybe<TooltipCopyCallback>;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Column.Props) {
|
||||
public shouldComponentUpdate(nextProps: ColumnProps) {
|
||||
return this.data !== nextProps.node.validator;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export class ValidatorColumn extends React.Component<Column.Props, {}> {
|
||||
);
|
||||
}
|
||||
|
||||
private onCopy = (copy: Tooltip.CopyCallback) => {
|
||||
private onCopy = (copy: TooltipCopyCallback) => {
|
||||
this.copy = copy;
|
||||
};
|
||||
|
||||
|
||||
@@ -29,25 +29,18 @@ const ROW_MARGIN = 5;
|
||||
|
||||
import './List.css';
|
||||
|
||||
export namespace List {
|
||||
export interface Props {
|
||||
appState: Readonly<AppState>;
|
||||
appUpdate: AppUpdate;
|
||||
pins: PersistentSet<Types.NodeName>;
|
||||
sortBy: Persistent<Maybe<number>>;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
filter: Maybe<(node: Node) => boolean>;
|
||||
viewportHeight: number;
|
||||
}
|
||||
interface ListProps {
|
||||
appState: Readonly<AppState>;
|
||||
appUpdate: AppUpdate;
|
||||
pins: PersistentSet<Types.NodeName>;
|
||||
sortBy: Persistent<Maybe<number>>;
|
||||
}
|
||||
|
||||
// Helper for readability, used as `key` prop for each `Row`
|
||||
// of the `List`, so that we can maximize re-using DOM elements.
|
||||
type Key = number;
|
||||
|
||||
export class List extends React.Component<List.Props, {}> {
|
||||
export class List extends React.Component<ListProps> {
|
||||
public state = {
|
||||
filter: null,
|
||||
viewportHeight: viewport().height,
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Types, Maybe } from '../../common';
|
||||
import { Types } from '../../common';
|
||||
import { Node } from '../../state';
|
||||
import { Persistent, PersistentSet } from '../../persist';
|
||||
import { PersistentSet } from '../../persist';
|
||||
import {
|
||||
Column,
|
||||
NameColumn,
|
||||
@@ -42,24 +42,17 @@ import {
|
||||
|
||||
import './Row.css';
|
||||
|
||||
export namespace Row {
|
||||
export interface Props {
|
||||
node: Node;
|
||||
pins: PersistentSet<Types.NodeName>;
|
||||
columns: Column[];
|
||||
}
|
||||
|
||||
export interface State {
|
||||
update: number;
|
||||
}
|
||||
}
|
||||
|
||||
interface HeaderProps {
|
||||
interface RowProps {
|
||||
node: Node;
|
||||
pins: PersistentSet<Types.NodeName>;
|
||||
columns: Column[];
|
||||
sortBy: Persistent<Maybe<number>>;
|
||||
}
|
||||
|
||||
export class Row extends React.Component<Row.Props, Row.State> {
|
||||
interface RowState {
|
||||
update: number;
|
||||
}
|
||||
|
||||
export class Row extends React.Component<RowProps, RowState> {
|
||||
public static readonly columns: Column[] = [
|
||||
NameColumn,
|
||||
ValidatorColumn,
|
||||
@@ -83,7 +76,7 @@ export class Row extends React.Component<Row.Props, Row.State> {
|
||||
|
||||
private renderedChangeRef = 0;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Row.Props): boolean {
|
||||
public shouldComponentUpdate(nextProps: RowProps): boolean {
|
||||
return (
|
||||
this.props.node.id !== nextProps.node.id ||
|
||||
this.renderedChangeRef !== nextProps.node.changeRef
|
||||
|
||||
@@ -39,4 +39,4 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
.THeadCell-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,23 +21,21 @@ import { Persistent } from '../../persist';
|
||||
|
||||
import './THead.css';
|
||||
|
||||
export namespace THead {
|
||||
export interface Props {
|
||||
columns: Column[];
|
||||
sortBy: Persistent<Maybe<number>>;
|
||||
}
|
||||
interface THeadProps {
|
||||
columns: Column[];
|
||||
sortBy: Persistent<Maybe<number>>;
|
||||
}
|
||||
|
||||
export class THead extends React.Component<THead.Props, {}> {
|
||||
export class THead extends React.Component<THeadProps> {
|
||||
private sortBy: Maybe<number>;
|
||||
|
||||
constructor(props: THead.Props) {
|
||||
constructor(props: THeadProps) {
|
||||
super(props);
|
||||
|
||||
this.sortBy = props.sortBy.get();
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: THead.Props) {
|
||||
public shouldComponentUpdate(nextProps: THeadProps) {
|
||||
return this.sortBy !== nextProps.sortBy.get();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,16 +23,14 @@ import { Persistent } from '../../persist';
|
||||
import sortAscIcon from '../../icons/triangle-up.svg';
|
||||
import sortDescIcon from '../../icons/triangle-down.svg';
|
||||
|
||||
export namespace THeadCell {
|
||||
export interface Props {
|
||||
column: Column;
|
||||
index: number;
|
||||
last: number;
|
||||
sortBy: Persistent<Maybe<number>>;
|
||||
}
|
||||
interface THeadCellProps {
|
||||
column: Column;
|
||||
index: number;
|
||||
last: number;
|
||||
sortBy: Persistent<Maybe<number>>;
|
||||
}
|
||||
|
||||
export class THeadCell extends React.Component<THeadCell.Props, {}> {
|
||||
export class THeadCell extends React.Component<THeadCellProps> {
|
||||
public render() {
|
||||
const { column, index, last } = this.props;
|
||||
const { icon, width, label } = column;
|
||||
|
||||
@@ -36,27 +36,25 @@ import lastTimeIcon from '../../icons/watch.svg';
|
||||
|
||||
import './Location.css';
|
||||
|
||||
export namespace Location {
|
||||
export type Quarter = 0 | 1 | 2 | 3;
|
||||
export type LocationQuarter = 0 | 1 | 2 | 3;
|
||||
|
||||
export interface Props {
|
||||
node: Node;
|
||||
position: Position;
|
||||
focused: boolean;
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
left: number;
|
||||
top: number;
|
||||
quarter: Quarter;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
hover: boolean;
|
||||
}
|
||||
interface LocationProps {
|
||||
node: Node;
|
||||
position: LocationPosition;
|
||||
focused: boolean;
|
||||
}
|
||||
|
||||
export class Location extends React.Component<Location.Props, Location.State> {
|
||||
export interface LocationPosition {
|
||||
left: number;
|
||||
top: number;
|
||||
quarter: LocationQuarter;
|
||||
}
|
||||
|
||||
interface LocationState {
|
||||
hover: boolean;
|
||||
}
|
||||
|
||||
export class Location extends React.Component<LocationProps, LocationState> {
|
||||
public readonly state = { hover: false };
|
||||
|
||||
public render() {
|
||||
|
||||
@@ -18,7 +18,7 @@ import * as React from 'react';
|
||||
import { Types, Maybe } from '../../common';
|
||||
import { Filter } from '../';
|
||||
import { State as AppState, Node } from '../../state';
|
||||
import { Location } from './';
|
||||
import { Location, LocationQuarter, LocationPosition } from './';
|
||||
import { viewport } from '../../utils';
|
||||
|
||||
const MAP_RATIO = 800 / 350;
|
||||
@@ -27,22 +27,20 @@ const HEADER = 148;
|
||||
|
||||
import './Map.css';
|
||||
|
||||
export namespace Map {
|
||||
export interface Props {
|
||||
appState: Readonly<AppState>;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
filter: Maybe<(node: Node) => boolean>;
|
||||
width: number;
|
||||
height: number;
|
||||
top: number;
|
||||
left: number;
|
||||
}
|
||||
interface MapProps {
|
||||
appState: Readonly<AppState>;
|
||||
}
|
||||
|
||||
export class Map extends React.Component<Map.Props, Map.State> {
|
||||
public state: Map.State = {
|
||||
interface MapState {
|
||||
filter: Maybe<(node: Node) => boolean>;
|
||||
width: number;
|
||||
height: number;
|
||||
top: number;
|
||||
left: number;
|
||||
}
|
||||
|
||||
export class Map extends React.Component<MapProps, MapState> {
|
||||
public state: MapState = {
|
||||
filter: null,
|
||||
width: 0,
|
||||
height: 0,
|
||||
@@ -98,7 +96,7 @@ export class Map extends React.Component<Map.Props, Map.State> {
|
||||
private pixelPosition(
|
||||
lat: Types.Latitude,
|
||||
lon: Types.Longitude
|
||||
): Location.Position {
|
||||
): LocationPosition {
|
||||
const { state } = this;
|
||||
|
||||
// Longitude ranges -180 (west) to +180 (east)
|
||||
@@ -108,14 +106,14 @@ export class Map extends React.Component<Map.Props, Map.State> {
|
||||
((90 - lat) / 180) * state.height * MAP_HEIGHT_ADJUST + state.top
|
||||
);
|
||||
|
||||
let quarter: Location.Quarter = 0;
|
||||
let quarter: LocationQuarter = 0;
|
||||
|
||||
if (lon > 0) {
|
||||
quarter = (quarter | 1) as Location.Quarter;
|
||||
quarter = (quarter | 1) as LocationQuarter;
|
||||
}
|
||||
|
||||
if (lat < 0) {
|
||||
quarter = (quarter | 2) as Location.Quarter;
|
||||
quarter = (quarter | 2) as LocationQuarter;
|
||||
}
|
||||
|
||||
return { left, top, quarter };
|
||||
|
||||
@@ -21,14 +21,13 @@ import { State } from '../state';
|
||||
import offlineIcon from '../icons/zap.svg';
|
||||
import upgradeIcon from '../icons/flame.svg';
|
||||
|
||||
export namespace OfflineIndicator {
|
||||
export interface Props {
|
||||
status: State['status'];
|
||||
}
|
||||
interface OfflineIndicatorProps {
|
||||
status: State['status'];
|
||||
}
|
||||
|
||||
export function OfflineIndicator(
|
||||
props: OfflineIndicator.Props
|
||||
props: OfflineIndicatorProps
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): React.ReactElement<any> | null {
|
||||
switch (props.status) {
|
||||
case 'online':
|
||||
|
||||
@@ -74,9 +74,7 @@ const OUTER_CIRCLE: Circle = {
|
||||
fill: '#eee',
|
||||
};
|
||||
|
||||
function getRotation(
|
||||
isSixPoint: boolean
|
||||
): {
|
||||
function getRotation(isSixPoint: boolean): {
|
||||
r: number;
|
||||
ro2: number;
|
||||
r3o4: number;
|
||||
@@ -184,25 +182,21 @@ function generate(address: string, isSixPoint = false): Circle[] {
|
||||
}
|
||||
|
||||
return [OUTER_CIRCLE].concat(
|
||||
getCircleXY(isSixPoint).map(
|
||||
([cx, cy], index): Circle => {
|
||||
return {
|
||||
cx,
|
||||
cy,
|
||||
r: Z,
|
||||
fill: colors[index] || 'rgb(255,255,255)',
|
||||
};
|
||||
}
|
||||
)
|
||||
getCircleXY(isSixPoint).map(([cx, cy], index): Circle => {
|
||||
return {
|
||||
cx,
|
||||
cy,
|
||||
r: Z,
|
||||
fill: colors[index] || 'rgb(255,255,255)',
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export namespace PolkadotIcon {
|
||||
export interface Props {
|
||||
account: string;
|
||||
size: number;
|
||||
className?: string;
|
||||
}
|
||||
interface PolkadotIconProps {
|
||||
account: string;
|
||||
size: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const rendered = new Set<string>();
|
||||
@@ -233,8 +227,8 @@ function renderShadowIcon(account: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export class PolkadotIcon extends React.Component<PolkadotIcon.Props, {}> {
|
||||
public shouldComponentUpdate(nextProps: PolkadotIcon.Props) {
|
||||
export class PolkadotIcon extends React.Component<PolkadotIconProps> {
|
||||
public shouldComponentUpdate(nextProps: PolkadotIconProps) {
|
||||
return (
|
||||
this.props.account !== nextProps.account ||
|
||||
this.props.size !== nextProps.size
|
||||
|
||||
@@ -16,21 +16,19 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Icon } from '../';
|
||||
import { State } from '../../state';
|
||||
import { StateSettings } from '../../state';
|
||||
import { PersistentObject } from '../../persist';
|
||||
|
||||
import './Setting.css';
|
||||
|
||||
export namespace Setting {
|
||||
export interface Props {
|
||||
icon: string;
|
||||
label: string;
|
||||
setting: keyof State.Settings;
|
||||
settings: PersistentObject<State.Settings>;
|
||||
}
|
||||
interface SettingProps {
|
||||
icon: string;
|
||||
label: string;
|
||||
setting: keyof StateSettings;
|
||||
settings: PersistentObject<StateSettings>;
|
||||
}
|
||||
|
||||
export class Setting extends React.Component<Setting.Props, {}> {
|
||||
export class Setting extends React.Component<SettingProps> {
|
||||
public render() {
|
||||
const { icon, label, setting, settings } = this.props;
|
||||
|
||||
|
||||
@@ -15,28 +15,18 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Maybe } from '../../common';
|
||||
import { State as AppState } from '../../state';
|
||||
import { StateSettings } from '../../state';
|
||||
import { Setting } from './';
|
||||
import { Row } from '../List';
|
||||
import { PersistentObject } from '../../persist';
|
||||
|
||||
import './Settings.css';
|
||||
|
||||
export namespace Settings {
|
||||
export type Display = 'list' | 'map' | 'settings';
|
||||
|
||||
export interface Props {
|
||||
settings: PersistentObject<AppState.Settings>;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
display: Display;
|
||||
filter: Maybe<string>;
|
||||
}
|
||||
interface SettingsProps {
|
||||
settings: PersistentObject<StateSettings>;
|
||||
}
|
||||
|
||||
export class Settings extends React.Component<Settings.Props, {}> {
|
||||
export class Settings extends React.Component<SettingsProps> {
|
||||
public render() {
|
||||
const { settings } = this.props;
|
||||
|
||||
|
||||
@@ -16,28 +16,26 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Types, Maybe } from '../common';
|
||||
import { Tooltip } from './';
|
||||
import { Tooltip, TooltipUpdateCallback } from './';
|
||||
|
||||
import './Sparkline.css';
|
||||
|
||||
export namespace Sparkline {
|
||||
export interface Props {
|
||||
stroke: number;
|
||||
width: number;
|
||||
height: number;
|
||||
values: number[];
|
||||
stamps?: Types.Timestamp[];
|
||||
minScale?: number;
|
||||
format?: (value: number, stamp: Maybe<Types.Timestamp>) => string;
|
||||
}
|
||||
interface SparklineProps {
|
||||
stroke: number;
|
||||
width: number;
|
||||
height: number;
|
||||
values: number[];
|
||||
stamps?: Types.Timestamp[];
|
||||
minScale?: number;
|
||||
format?: (value: number, stamp: Maybe<Types.Timestamp>) => string;
|
||||
}
|
||||
|
||||
export class Sparkline extends React.Component<Sparkline.Props, {}> {
|
||||
export class Sparkline extends React.Component<SparklineProps> {
|
||||
private cursor: SVGPathElement;
|
||||
private update: Tooltip.UpdateCallback;
|
||||
private update: TooltipUpdateCallback;
|
||||
|
||||
public shouldComponentUpdate(nextProps: Sparkline.Props): boolean {
|
||||
const { stroke, width, height, minScale, format, values } = this.props;
|
||||
public shouldComponentUpdate(nextProps: SparklineProps): boolean {
|
||||
const { stroke, width, height, format, values } = this.props;
|
||||
|
||||
return (
|
||||
values !== nextProps.values ||
|
||||
@@ -93,7 +91,7 @@ export class Sparkline extends React.Component<Sparkline.Props, {}> {
|
||||
this.cursor = cursor;
|
||||
};
|
||||
|
||||
private onTooltipInit = (update: Tooltip.UpdateCallback) => {
|
||||
private onTooltipInit = (update: TooltipUpdateCallback) => {
|
||||
this.update = update;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,18 +17,12 @@
|
||||
import * as React from 'react';
|
||||
import { Maybe } from '../../common';
|
||||
import { State as AppState } from '../../state';
|
||||
import { Row } from '../List';
|
||||
import { PersistentObject } from '../../persist';
|
||||
import { Ranking, Range } from '../../common/types';
|
||||
|
||||
import './Stats.css';
|
||||
|
||||
export namespace Stats {
|
||||
export type Display = 'list' | 'map' | 'Stats';
|
||||
|
||||
export interface Props {
|
||||
appState: Readonly<AppState>;
|
||||
}
|
||||
interface StatsProps {
|
||||
appState: Readonly<AppState>;
|
||||
}
|
||||
|
||||
function displayPercentage(percent: number): string {
|
||||
@@ -138,7 +132,7 @@ function formatScore(value: Range): string {
|
||||
return (min / 100).toFixed(1) + 'x';
|
||||
}
|
||||
|
||||
export class Stats extends React.Component<Stats.Props, {}> {
|
||||
export class Stats extends React.Component<StatsProps> {
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
|
||||
|
||||
@@ -18,15 +18,13 @@ import * as React from 'react';
|
||||
import './Tile.css';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
export namespace Tile {
|
||||
export interface Props {
|
||||
title: string;
|
||||
icon: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
interface TileProps {
|
||||
title: string;
|
||||
icon: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Tile(props: Tile.Props) {
|
||||
export function Tile(props: TileProps) {
|
||||
return (
|
||||
<div className="Tile">
|
||||
<Icon src={props.icon} />
|
||||
|
||||
@@ -95,4 +95,3 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
:hover > .Tooltip {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,22 +19,16 @@ import { Maybe } from '../common';
|
||||
|
||||
import './Tooltip.css';
|
||||
|
||||
export namespace Tooltip {
|
||||
export interface Props {
|
||||
text: string;
|
||||
copy?: (cb: CopyCallback) => void;
|
||||
position?: 'left' | 'right' | 'center';
|
||||
onInit?: (update: UpdateCallback) => void;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
copied: boolean;
|
||||
}
|
||||
|
||||
export type UpdateCallback = (text: string) => void;
|
||||
export type CopyCallback = Maybe<() => void>;
|
||||
interface TooltipProps {
|
||||
text: string;
|
||||
copy?: (cb: TooltipCopyCallback) => void;
|
||||
position?: 'left' | 'right' | 'center';
|
||||
onInit?: (update: TooltipUpdateCallback) => void;
|
||||
}
|
||||
|
||||
export type TooltipUpdateCallback = (text: string) => void;
|
||||
export type TooltipCopyCallback = Maybe<() => void>;
|
||||
|
||||
function copyToClipboard(text: string) {
|
||||
const el = document.createElement('textarea');
|
||||
el.value = text;
|
||||
@@ -49,7 +43,7 @@ export function Tooltip({
|
||||
position,
|
||||
copy,
|
||||
onInit,
|
||||
}: Tooltip.Props): JSX.Element {
|
||||
}: TooltipProps): JSX.Element {
|
||||
const [copied, setCopied] = React.useState<boolean>(false);
|
||||
const [timer, setTimer] = React.useState<NodeJS.Timer | null>(null);
|
||||
const el = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -16,15 +16,13 @@
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
export namespace Truncate {
|
||||
export interface Props {
|
||||
text: string;
|
||||
chars?: number;
|
||||
}
|
||||
interface TruncateProps {
|
||||
text: string;
|
||||
chars?: number;
|
||||
}
|
||||
|
||||
export class Truncate extends React.Component<Truncate.Props, {}> {
|
||||
public shouldComponentUpdate(nextProps: Truncate.Props): boolean {
|
||||
export class Truncate extends React.Component<TruncateProps> {
|
||||
public shouldComponentUpdate(nextProps: TruncateProps): boolean {
|
||||
return this.props.text !== nextProps.text;
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg height='300px' width='300px' xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" x="0px" y="0px" viewBox="0 0 100 100"><g transform="translate(0,-952.36218)"><path style="enable-background:accumulate;" d="m 50.000002,960.75284 -36.25,14.28129 36.25,14.2812 36.249996,-14.2812 -36.249996,-14.28129 z m -39,17.49999 0,51.12497 37,14.5937 0,-51.12487 -37,-14.5938 z m 77.999996,0 -36.999996,14.5938 0,51.12487 36.999996,-14.5937 0,-51.12497 z" stroke="none" marker="none" visibility="visible" display="inline" overflow="visible"></path></g></svg>
|
||||
<svg height='300px' width='300px' xmlnsDc="http://purl.org/dc/elements/1.1/" xmlnsCc="http://creativecommons.org/ns#" xmlnsRdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlnsSvg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlnsSodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlnsInkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" x="0px" y="0px" viewBox="0 0 100 100"><g transform="translate(0,-952.36218)"><path style="enable-background:accumulate;" d="m 50.000002,960.75284 -36.25,14.28129 36.25,14.2812 36.249996,-14.2812 -36.249996,-14.28129 z m -39,17.49999 0,51.12497 37,14.5937 0,-51.12487 -37,-14.5938 z m 77.999996,0 -36.999996,14.5938 0,51.12487 36.999996,-14.5937 0,-51.12497 z" stroke="none" marker="none" visibility="visible" display="inline" overflow="visible"></path></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 856 B After Width: | Height: | Size: 850 B |
@@ -1 +1 @@
|
||||
<svg height='300px' width='300px' xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" x="0px" y="0px" viewBox="0 0 100 100"><g transform="translate(0,-952.36218)"><path style="text-indent:0;text-transform:none;direction:ltr;block-progression:tb;baseline-shift:baseline;enable-background:accumulate;" d="m 49.625002,958.37952 a 3.0003,3.0003 0 0 0 -0.7188,0.1875 l -37,14.71875 a 3.0003,3.0003 0 0 0 -1.9062,2.8125 l 0,52.56253 a 3.0003,3.0003 0 0 0 1.9062,2.7812 l 37,14.7188 a 3.0003,3.0003 0 0 0 2.1876,0 l 36.999997,-14.7188 a 3.0003,3.0003 0 0 0 1.9062,-2.7812 l 0,-52.56253 a 3.0003,3.0003 0 0 0 -1.9062,-2.8125 L 51.093802,958.56702 a 3.0003,3.0003 0 0 0 -1.4688,-0.1875 z m 0.375,6.21875 28.875,11.5 -28.875,11.46873 -28.875,-11.46873 28.875,-11.5 z m -34,15.90623 31,12.3438 0,46.0937 -31,-12.3437 0,-46.0938 z m 68,0 0,46.0938 -31,12.3437 0,-46.0937 31,-12.3438 z" fill-opacity="1" stroke="none" marker="none" visibility="visible" display="inline" overflow="visible"></path></g></svg>
|
||||
<svg height='300px' width='300px' xmlnsDc="http://purl.org/dc/elements/1.1/" xmlnsCc="http://creativecommons.org/ns#" xmlnsRdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlnsSvg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlnsSodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlnsInkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" x="0px" y="0px" viewBox="0 0 100 100"><g transform="translate(0,-952.36218)"><path style="text-indent:0;text-transform:none;direction:ltr;block-progression:tb;baseline-shift:baseline;enable-background:accumulate;" d="m 49.625002,958.37952 a 3.0003,3.0003 0 0 0 -0.7188,0.1875 l -37,14.71875 a 3.0003,3.0003 0 0 0 -1.9062,2.8125 l 0,52.56253 a 3.0003,3.0003 0 0 0 1.9062,2.7812 l 37,14.7188 a 3.0003,3.0003 0 0 0 2.1876,0 l 36.999997,-14.7188 a 3.0003,3.0003 0 0 0 1.9062,-2.7812 l 0,-52.56253 a 3.0003,3.0003 0 0 0 -1.9062,-2.8125 L 51.093802,958.56702 a 3.0003,3.0003 0 0 0 -1.4688,-0.1875 z m 0.375,6.21875 28.875,11.5 -28.875,11.46873 -28.875,-11.46873 28.875,-11.5 z m -34,15.90623 31,12.3438 0,46.0937 -31,-12.3437 0,-46.0938 z m 68,0 0,46.0938 -31,12.3437 0,-46.0937 31,-12.3438 z" fill-opacity="1" stroke="none" marker="none" visibility="visible" display="inline" overflow="visible"></path></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,5 +1,5 @@
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlnsSvg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="12"
|
||||
height="16"
|
||||
|
||||
|
Before Width: | Height: | Size: 314 B After Width: | Height: | Size: 313 B |
@@ -2,7 +2,7 @@
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlnsSvg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100mm"
|
||||
height="100mm"
|
||||
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -15,11 +15,13 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
import { unregister } from './registerServiceWorker';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root') as HTMLElement);
|
||||
const container = document.getElementById('root') as HTMLElement;
|
||||
const root = createRoot(container);
|
||||
root.render(<App />);
|
||||
|
||||
unregister();
|
||||
|
||||
@@ -43,7 +43,8 @@ export class Persistent<Data> {
|
||||
|
||||
window.addEventListener('storage', (event) => {
|
||||
if (event.key === this.key) {
|
||||
this.value = parse((event.newValue as any) as Stringified<Data>);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
this.value = parse(event.newValue as any as Stringified<Data>);
|
||||
|
||||
this.onChange(this.value);
|
||||
}
|
||||
@@ -58,7 +59,8 @@ export class Persistent<Data> {
|
||||
this.value = value;
|
||||
window.localStorage.setItem(
|
||||
this.key,
|
||||
(stringify(this.value) as any) as string
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
stringify(this.value) as any as string
|
||||
);
|
||||
this.onChange(this.value);
|
||||
}
|
||||
|
||||
@@ -237,31 +237,24 @@ export function bindState(bind: React.Component, state: State): Update {
|
||||
};
|
||||
}
|
||||
|
||||
export namespace State {
|
||||
export interface Settings {
|
||||
location: boolean;
|
||||
validator: boolean;
|
||||
implementation: boolean;
|
||||
networkId: boolean;
|
||||
peers: boolean;
|
||||
txs: boolean;
|
||||
upload: boolean;
|
||||
download: boolean;
|
||||
stateCacheSize: boolean;
|
||||
blocknumber: boolean;
|
||||
blockhash: boolean;
|
||||
finalized: boolean;
|
||||
finalizedhash: boolean;
|
||||
blocktime: boolean;
|
||||
blockpropagation: boolean;
|
||||
blocklasttime: boolean;
|
||||
uptime: boolean;
|
||||
}
|
||||
|
||||
export interface SortBy {
|
||||
column: string;
|
||||
reverse: boolean;
|
||||
}
|
||||
export interface StateSettings {
|
||||
location: boolean;
|
||||
validator: boolean;
|
||||
implementation: boolean;
|
||||
networkId: boolean;
|
||||
peers: boolean;
|
||||
txs: boolean;
|
||||
upload: boolean;
|
||||
download: boolean;
|
||||
stateCacheSize: boolean;
|
||||
blocknumber: boolean;
|
||||
blockhash: boolean;
|
||||
finalized: boolean;
|
||||
finalizedhash: boolean;
|
||||
blocktime: boolean;
|
||||
blockpropagation: boolean;
|
||||
blocklasttime: boolean;
|
||||
uptime: boolean;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
@@ -275,7 +268,7 @@ export interface State {
|
||||
subscribed: Maybe<Types.GenesisHash>;
|
||||
chains: Map<Types.GenesisHash, ChainData>;
|
||||
nodes: SortedCollection<Node>;
|
||||
settings: Readonly<State.Settings>;
|
||||
settings: Readonly<StateSettings>;
|
||||
pins: Readonly<Set<Types.NodeName>>;
|
||||
sortBy: Readonly<Maybe<number>>;
|
||||
selectedColumns: Column[];
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
"outDir": "build",
|
||||
"module": "esnext",
|
||||
"target": "ES2017",
|
||||
"lib": ["es6", "dom"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"allowJs": false,
|
||||
"jsx": "react",
|
||||
@@ -25,5 +29,5 @@
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"exclude": ["node_modules", "build"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "declarations/**/*.d.ts"]
|
||||
"include": ["src", "declarations/**/*.d.ts"]
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"tslint:recommended",
|
||||
"tslint-react",
|
||||
"tslint-plugin-prettier",
|
||||
"tslint-config-prettier"
|
||||
],
|
||||
"linterOptions": {
|
||||
"exclude": ["config/**/*.js", "node_modules/**/*.ts"]
|
||||
},
|
||||
"rules": {
|
||||
"prettier": true,
|
||||
"ordered-imports": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"no-console": false,
|
||||
"no-empty": false,
|
||||
"no-namespace": false,
|
||||
"no-bitwise": false,
|
||||
"quotemark": [true, "single", "jsx-double"],
|
||||
"semicolon": [true, "always", "ignore-bound-class-methods"],
|
||||
"interface-name": false,
|
||||
"array-type": false,
|
||||
"max-classes-per-file": false,
|
||||
"variable-name": [
|
||||
true,
|
||||
"allow-leading-underscore"
|
||||
]
|
||||
}
|
||||
}
|
||||