Allow to pin nodes to top of the list (#48)

* Refactored persistent state a bit
* Allow nodes to be pinned to top
This commit is contained in:
Maciej Hirsz
2018-09-24 17:30:39 +02:00
committed by GitHub
parent 44d91a54d5
commit 1559b82eb0
24 changed files with 194 additions and 86 deletions
@@ -80,7 +80,7 @@
.Chain-node-list th, .Chain-node-list td {
text-align: left;
padding: 0.5em 1em;
padding: 0.35em 1em;
}
.Chain-settings {
@@ -2,9 +2,9 @@ import * as React from 'react';
import { State as AppState } from '../../state';
import { formatNumber, secondsWithPrecision, viewport } from '../../utils';
import { Tab } from './';
import { Tile, Node, Ago, Option } from '../';
import { Tile, Node, Ago, Setting } from '../';
import { Types } from '@dotstats/common';
import { Persistent } from '../../Persistent';
import { PersistentObject, PersistentSet } from '../../persist';
import blockIcon from '../../icons/package.svg';
import blockTimeIcon from '../../icons/history.svg';
@@ -24,7 +24,8 @@ export namespace Chain {
export interface Props {
appState: Readonly<AppState>;
setSettings: Persistent<AppState.Settings>['set'];
settings: PersistentObject<AppState.Settings>;
pins: PersistentSet<Types.NodeId>;
}
export interface State {
@@ -39,12 +40,16 @@ export namespace Chain {
}
function sortNodes(a: AppState.Node, b: AppState.Node): number {
if (a.blockDetails[0] === b.blockDetails[0]) {
const aPropagation = a.blockDetails[4] == null ? Infinity : a.blockDetails[4] as number;
const bPropagation = b.blockDetails[4] == null ? Infinity : b.blockDetails[4] as number;
if (a.pinned === b.pinned) {
if (a.blockDetails[0] === b.blockDetails[0]) {
const aPropagation = a.blockDetails[4] == null ? Infinity : a.blockDetails[4] as number;
const bPropagation = b.blockDetails[4] == null ? Infinity : b.blockDetails[4] as number;
// Ascending sort by propagation time
return aPropagation - bPropagation;
// Ascending sort by propagation time
return aPropagation - bPropagation;
}
} else {
return Number(b.pinned) - Number(a.pinned);
}
// Descending sort by block number
@@ -124,6 +129,7 @@ export class Chain extends React.Component<Chain.Props, Chain.State> {
private renderList() {
const { settings } = this.props.appState;
const { pins } = this.props;
return (
<table className="Chain-node-list">
@@ -133,7 +139,7 @@ export class Chain extends React.Component<Chain.Props, Chain.State> {
this
.nodes()
.sort(sortNodes)
.map((node) => <Node.Row key={node.id} node={node} settings={settings} />)
.map((node) => <Node.Row key={node.id} node={node} settings={settings} pins={pins} />)
}
</tbody>
</table>
@@ -164,7 +170,7 @@ export class Chain extends React.Component<Chain.Props, Chain.State> {
}
private renderSettings() {
const { settings } = this.props.appState;
const { settings } = this.props;
return (
<div className="Chain-settings">
@@ -177,17 +183,7 @@ export class Chain extends React.Component<Chain.Props, Chain.State> {
return null;
}
const checked = settings[setting];
const changeSetting = () => {
const change = {};
change[setting] = !settings[setting];
this.props.setSettings(change);
}
return <Option key={index} onClick={changeSetting} icon={icon} label={label} checked={checked} />;
return <Setting key={index} setting={setting} settings={settings} icon={icon} label={label} />;
})
}
</div>
@@ -7,10 +7,6 @@
position: relative;
}
.Chains .Icon {
margin-right: 1em;
}
.Chains-chain {
padding: 0 12px;
background: #bbb;
@@ -3,7 +3,6 @@ import { Connection } from '../Connection';
import { Icon } from './Icon';
import { Types, Maybe } from '@dotstats/common';
import chainIcon from '../icons/link.svg';
import githubIcon from '../icons/mark-github.svg';
import './Chains.css';
@@ -24,7 +23,6 @@ export class Chains extends React.Component<Chains.Props, {}> {
public render() {
return (
<div className="Chains">
<Icon src={chainIcon} alt="Observed Chain" />
{
this.chains.map((chain) => this.renderChain(chain))
}
+5 -3
View File
@@ -4,7 +4,7 @@ import './Icon.css';
export interface Props {
src: string;
alt: string;
alt?: string;
className?: string;
onClick?: () => void;
};
@@ -12,8 +12,10 @@ export interface Props {
export class Icon extends React.Component<{}, Props> {
public props: Props;
public shouldComponentUpdate() {
return false;
public shouldComponentUpdate(nextProps: Props) {
return this.props.src !== nextProps.src
|| this.props.alt !== nextProps.alt
|| this.props.className !== nextProps.className;
}
public render() {
@@ -1,5 +1,6 @@
.Node-Row {
color: #999;
cursor: pointer;
}
.Node-Row-synced {
+24 -2
View File
@@ -1,10 +1,15 @@
import * as React from 'react';
import Identicon from 'polkadot-identicon';
import { Types } from '@dotstats/common';
import { formatNumber, trimHash, milliOrSecond, secondsWithPrecision } from '../../utils';
import { State as AppState } from '../../state';
import { PersistentSet } from '../../persist';
import { SEMVER_PATTERN } from './';
import { Ago, Icon } from '../';
import pinIcon from '../../icons/pin.svg';
import pinOnIcon from '../../icons/check-square-solid.svg';
import pinOffIcon from '../../icons/square-solid.svg';
import nodeIcon from '../../icons/server.svg';
import nodeValidatorIcon from '../../icons/shield.svg';
import nodeTypeIcon from '../../icons/terminal.svg';
@@ -23,6 +28,7 @@ import './Row.css';
interface RowProps {
node: AppState.Node;
settings: AppState.Settings;
pins: PersistentSet<Types.NodeId>;
};
interface HeaderProps {
@@ -39,6 +45,12 @@ interface Column {
export default class Row extends React.Component<RowProps, {}> {
public static readonly columns: Column[] = [
{
label: 'Pin to Top',
icon: pinIcon,
width: 16,
render: ({ pinned }) => <Icon src={pinned ? pinOnIcon : pinOffIcon} />
},
{
label: 'Node',
icon: nodeIcon,
@@ -93,7 +105,7 @@ export default class Row extends React.Component<RowProps, {}> {
}
},
{
label: 'Memory use',
label: 'Memory Use',
icon: memoryIcon,
width: 26,
setting: 'mem',
@@ -177,7 +189,7 @@ export default class Row extends React.Component<RowProps, {}> {
}
return (
<tr className={className}>
<tr className={className} onClick={this.toggle}>
{
Row.columns
.filter(({ setting }) => setting == null || settings[setting])
@@ -186,4 +198,14 @@ export default class Row extends React.Component<RowProps, {}> {
</tr>
);
}
public toggle = () => {
const { pins, node } = this.props;
if (node.pinned) {
pins.delete(node.id)
} else {
pins.add(node.id);
}
}
}
@@ -1,27 +0,0 @@
import * as React from 'react';
import { Icon } from './';
import './Option.css';
export namespace Option {
export interface Props {
icon: string;
label: string;
checked: boolean;
onClick: () => void;
}
}
export function Option(props: Option.Props): React.ReactElement<any> {
const className = props.checked ? "Option Option-on" : "Option";
return (
<p className={className} onClick={props.onClick}>
<Icon src={props.icon} alt={props.label} />
{props.label}
<span className="Option-switch">
<span className="Option-knob" />
</span>
</p>
);
}
@@ -1,19 +1,19 @@
.Option {
.Setting {
color: #666;
padding: 0;
margin: 0 0 8px 0;
cursor: pointer;
}
.Option-on {
.Setting-on {
color: #fff;
}
.Option .Icon {
.Setting .Icon {
margin-right: 10px;
}
.Option-switch {
.Setting-switch {
width: 40px;
height: 18px;
border-radius: 18px;
@@ -24,12 +24,12 @@
transition: background-color 0.15s linear, border-color 0.15s linear;
}
.Option-on .Option-switch {
.Setting-on .Setting-switch {
background: #d64ca8;
border-color: #d64ca8;
}
.Option-knob {
.Setting-knob {
width: 16px;
height: 16px;
border: 1px solid #fff;
@@ -42,6 +42,6 @@
transition: left 0.15s ease-in-out;
}
.Option-on .Option-knob {
.Setting-on .Setting-knob {
left: 22px;
}
@@ -0,0 +1,40 @@
import * as React from 'react';
import { Icon } from './';
import { State } 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>;
}
}
export class Setting extends React.Component<Setting.Props, {}> {
public render() {
const { icon, label, setting, settings } = this.props;
const checked = settings.get(setting);
const className = checked ? "Setting Setting-on" : "Setting";
return (
<p className={className} onClick={this.toggle}>
<Icon src={icon} alt={label} />
{label}
<span className="Setting-switch">
<span className="Setting-knob" />
</span>
</p>
);
}
private toggle = () => {
const { setting, settings } = this.props;
settings.set(setting, !settings.get(setting));
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ export * from './Icon';
export * from './Tile';
export * from './Ago';
export * from './OfflineIndicator';
export * from './Option';
export * from './Setting';
import * as Node from './Node';