mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-05-06 03:07:58 +00:00
Remove Filter from Chain component (#89)
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import { Types, Maybe } from '@dotstats/common';
|
||||
import { State as AppState, Node as NodeState } from '../../state';
|
||||
import { Types } from '@dotstats/common';
|
||||
import { State as AppState } from '../../state';
|
||||
import { formatNumber, secondsWithPrecision, getHashData } from '../../utils';
|
||||
import { Tab, Filter } from './';
|
||||
import { Tab } from './';
|
||||
import { Tile, Ago, List, Map, Settings } from '../';
|
||||
import { PersistentObject, PersistentSet } from '../../persist';
|
||||
|
||||
@@ -13,8 +13,6 @@ import listIcon from '../../icons/list-alt-regular.svg';
|
||||
import worldIcon from '../../icons/location.svg';
|
||||
import settingsIcon from '../../icons/settings.svg';
|
||||
|
||||
const ESCAPE_KEY = 27;
|
||||
|
||||
import './Chain.css';
|
||||
|
||||
export namespace Chain {
|
||||
@@ -28,7 +26,6 @@ export namespace Chain {
|
||||
|
||||
export interface State {
|
||||
display: Display;
|
||||
filter: Maybe<string>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,18 +46,9 @@ export class Chain extends React.Component<Chain.Props, Chain.State> {
|
||||
|
||||
this.state = {
|
||||
display,
|
||||
filter: null,
|
||||
};
|
||||
}
|
||||
|
||||
public componentWillMount() {
|
||||
window.addEventListener('keyup', this.onKeyUp);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
window.removeEventListener('keyup', this.onKeyUp);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
const { best, blockTimestamp, blockAverage } = appState;
|
||||
@@ -88,7 +76,7 @@ export class Chain extends React.Component<Chain.Props, Chain.State> {
|
||||
}
|
||||
|
||||
private renderContent() {
|
||||
const { display, filter } = this.state;
|
||||
const { display } = this.state;
|
||||
|
||||
if (display === 'settings') {
|
||||
return <Settings settings={this.props.settings} />;
|
||||
@@ -97,57 +85,13 @@ export class Chain extends React.Component<Chain.Props, Chain.State> {
|
||||
const { appState, pins } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Filter value={filter} onChange={this.onFilterChange} />
|
||||
{
|
||||
display === 'list'
|
||||
? <List filter={this.getNodeFilter()} appState={appState} pins={pins} />
|
||||
: <Map filter={this.getNodeFilter()} appState={appState} />
|
||||
}
|
||||
</React.Fragment>
|
||||
display === 'list'
|
||||
? <List appState={appState} pins={pins} />
|
||||
: <Map appState={appState} />
|
||||
);
|
||||
}
|
||||
|
||||
private setDisplay = (display: Chain.Display) => {
|
||||
this.setState({ display });
|
||||
};
|
||||
|
||||
private onKeyUp = (event: KeyboardEvent) => {
|
||||
if (event.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { filter } = this.state;
|
||||
const key = event.key;
|
||||
|
||||
const escape = filter != null && event.keyCode === ESCAPE_KEY;
|
||||
const singleChar = filter == null && key.length === 1;
|
||||
|
||||
if (escape) {
|
||||
this.setState({ filter: null });
|
||||
} else if (singleChar) {
|
||||
this.setState({ filter: key });
|
||||
}
|
||||
}
|
||||
|
||||
private onFilterChange = (filter: string) => {
|
||||
this.setState({ filter });
|
||||
}
|
||||
|
||||
private getNodeFilter(): Maybe<(node: NodeState) => boolean> {
|
||||
const { filter } = this.state;
|
||||
|
||||
if (filter == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filterLC = filter.toLowerCase();
|
||||
|
||||
return ({ name, city }) => {
|
||||
const matchesName = name.toLowerCase().indexOf(filterLC) !== -1;
|
||||
const matchesCity = city != null && city.toLowerCase().indexOf(filterLC) !== -1;
|
||||
|
||||
return matchesName || matchesCity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { Maybe } from '@dotstats/common';
|
||||
import { Icon } from '../';
|
||||
|
||||
import searchIcon from '../../icons/search.svg';
|
||||
|
||||
import './Filter.css';
|
||||
|
||||
export namespace Filter {
|
||||
export interface Props {
|
||||
value: Maybe<string>;
|
||||
onChange: (value: Maybe<string>) => void;
|
||||
}
|
||||
}
|
||||
|
||||
const ESCAPE_KEY = 27;
|
||||
|
||||
export class Filter extends React.Component<Filter.Props, {}> {
|
||||
private filterInput: HTMLInputElement;
|
||||
|
||||
public componentDidMount() {
|
||||
this.filterInput.focus();
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: Filter.Props): boolean {
|
||||
if (this.props.value === nextProps.value && this.props.onChange === nextProps.onChange) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.props.value == null) {
|
||||
this.filterInput.focus();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { value } = this.props;
|
||||
|
||||
let className = "Filter";
|
||||
|
||||
if (value == null) {
|
||||
className += " Filter-hidden";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Icon src={searchIcon} />
|
||||
<input ref={this.onRef} value={value || ''} onChange={this.onChange} onKeyUp={this.onKeyUp} onBlur={this.onBlur} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private onRef = (el: HTMLInputElement) => {
|
||||
this.filterInput = el;
|
||||
}
|
||||
|
||||
private onChange = () => {
|
||||
const { value } = this.filterInput;
|
||||
|
||||
this.props.onChange(value === '' ? null : value);
|
||||
}
|
||||
|
||||
private onKeyUp = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (event.keyCode === ESCAPE_KEY) {
|
||||
this.props.onChange(null);
|
||||
}
|
||||
}
|
||||
|
||||
private onBlur = (event: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (this.props.value == null) {
|
||||
this.filterInput.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './Chain';
|
||||
export * from './Tab';
|
||||
export * from './Filter';
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import * as React from 'react';
|
||||
import { Maybe } from '@dotstats/common';
|
||||
import { Node } from '../state';
|
||||
import { Icon } from './';
|
||||
|
||||
import searchIcon from '../icons/search.svg';
|
||||
|
||||
import './Filter.css';
|
||||
|
||||
export namespace Filter {
|
||||
export interface Props {
|
||||
onChange: (value: Maybe<(node: Node) => boolean>) => void;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
value: string;
|
||||
}
|
||||
}
|
||||
|
||||
const ESCAPE_KEY = 27;
|
||||
|
||||
export class Filter extends React.Component<Filter.Props, {}> {
|
||||
public state = {
|
||||
value: ''
|
||||
};
|
||||
|
||||
private filterInput: HTMLInputElement;
|
||||
|
||||
public componentWillMount() {
|
||||
window.addEventListener('keyup', this.onWindowKeyUp);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
window.removeEventListener('keyup', this.onWindowKeyUp);
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: Filter.Props, nextState: Filter.State): boolean {
|
||||
return this.props.onChange !== nextProps.onChange || this.state.value !== nextState.value;
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { value } = this.state;
|
||||
|
||||
let className = "Filter";
|
||||
|
||||
if (value === '') {
|
||||
className += " Filter-hidden";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Icon src={searchIcon} />
|
||||
<input ref={this.onRef} value={value} onChange={this.onChange} onKeyUp={this.onKeyUp} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private setValue(value: string) {
|
||||
this.setState({ value });
|
||||
|
||||
this.props.onChange(this.getNodeFilter(value));
|
||||
}
|
||||
|
||||
private onRef = (el: HTMLInputElement) => {
|
||||
this.filterInput = el;
|
||||
}
|
||||
|
||||
private onChange = () => {
|
||||
this.setValue(this.filterInput.value);
|
||||
}
|
||||
|
||||
private onKeyUp = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (event.keyCode === ESCAPE_KEY) {
|
||||
this.setValue('');
|
||||
}
|
||||
}
|
||||
|
||||
private onWindowKeyUp = (event: KeyboardEvent) => {
|
||||
if (event.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { value } = this.state;
|
||||
const key = event.key;
|
||||
|
||||
const escape = value && event.keyCode === ESCAPE_KEY;
|
||||
const singleChar = value === '' && key.length === 1;
|
||||
|
||||
if (escape) {
|
||||
this.setValue('');
|
||||
} else if (singleChar) {
|
||||
this.setValue(key);
|
||||
this.filterInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private getNodeFilter(value: string): Maybe<(node: Node) => boolean> {
|
||||
if (value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filter = value.toLowerCase();
|
||||
|
||||
return ({ name, city }) => {
|
||||
const matchesName = name.toLowerCase().indexOf(filter) !== -1;
|
||||
const matchesCity = city != null && city.toLowerCase().indexOf(filter) !== -1;
|
||||
|
||||
return matchesName || matchesCity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Types, Maybe } from '@dotstats/common';
|
||||
import { Filter } from '../';
|
||||
import { State as AppState, Node } from '../../state';
|
||||
import { Row } from './';
|
||||
import { PersistentSet } from '../../persist';
|
||||
@@ -14,12 +15,12 @@ import './List.css';
|
||||
|
||||
export namespace List {
|
||||
export interface Props {
|
||||
filter: Maybe<(node: Node) => boolean>;
|
||||
appState: Readonly<AppState>;
|
||||
pins: PersistentSet<Types.NodeName>;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
filter: Maybe<(node: Node) => boolean>;
|
||||
viewportHeight: number;
|
||||
listStart: number;
|
||||
listEnd: number;
|
||||
@@ -28,6 +29,7 @@ export namespace List {
|
||||
|
||||
export class List extends React.Component<List.Props, {}> {
|
||||
public state = {
|
||||
filter: null,
|
||||
viewportHeight: viewport().height,
|
||||
listStart: 0,
|
||||
listEnd: 0,
|
||||
@@ -50,7 +52,8 @@ export class List extends React.Component<List.Props, {}> {
|
||||
|
||||
public render() {
|
||||
const { settings } = this.props.appState;
|
||||
const { pins, filter } = this.props;
|
||||
const { pins } = this.props;
|
||||
const { filter } = this.state;
|
||||
const columns = Row.columns.filter(({ setting }) => setting == null || settings[setting]);
|
||||
|
||||
let nodes = this.props.appState.nodes.sorted();
|
||||
@@ -60,7 +63,10 @@ export class List extends React.Component<List.Props, {}> {
|
||||
|
||||
if (nodes.length === 0) {
|
||||
return (
|
||||
<div className="List List-no-nodes">¯\_(ツ)_/¯<br />Nothing matches</div>
|
||||
<React.Fragment>
|
||||
<div className="List List-no-nodes">¯\_(ツ)_/¯<br />Nothing matches</div>
|
||||
<Filter onChange={this.onFilterChange} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -73,16 +79,19 @@ export class List extends React.Component<List.Props, {}> {
|
||||
nodes = nodes.slice(listStart, listEnd);
|
||||
|
||||
return (
|
||||
<div className="List" style={{ height }}>
|
||||
<table>
|
||||
<Row.Header columns={columns} />
|
||||
<tbody style={{ transform }}>
|
||||
{
|
||||
nodes.map((node) => <Row key={node.id} node={node} pins={pins} columns={columns} />)
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<React.Fragment>
|
||||
<div className="List" style={{ height }}>
|
||||
<table>
|
||||
<Row.Header columns={columns} />
|
||||
<tbody style={{ transform }}>
|
||||
{
|
||||
nodes.map((node) => <Row key={node.id} node={node} pins={pins} columns={columns} />)
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<Filter onChange={this.onFilterChange} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,6 +132,10 @@ export class List extends React.Component<List.Props, {}> {
|
||||
|
||||
this.setState({ viewportHeight });
|
||||
}
|
||||
|
||||
private onFilterChange = (filter: Maybe<(node: Node) => boolean>) => {
|
||||
this.setState({ filter });
|
||||
}
|
||||
}
|
||||
|
||||
function divisibleBy(n: number, dividor: number): number {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Types, Maybe } from '@dotstats/common';
|
||||
import { Filter } from '../';
|
||||
import { State as AppState, Node } from '../../state';
|
||||
import { Location } from './';
|
||||
import { viewport } from '../../utils';
|
||||
@@ -12,11 +13,11 @@ import './Map.css';
|
||||
|
||||
export namespace Map {
|
||||
export interface Props {
|
||||
filter: Maybe<(node: Node) => boolean>;
|
||||
appState: Readonly<AppState>;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
filter: Maybe<(node: Node) => boolean>;
|
||||
width: number;
|
||||
height: number;
|
||||
top: number;
|
||||
@@ -25,7 +26,8 @@ export namespace Map {
|
||||
}
|
||||
|
||||
export class Map extends React.Component<Map.Props, Map.State> {
|
||||
public state = {
|
||||
public state: Map.State = {
|
||||
filter: null,
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
@@ -43,29 +45,34 @@ export class Map extends React.Component<Map.Props, Map.State> {
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { filter, appState } = this.props;
|
||||
const { appState } = this.props;
|
||||
const { filter } = this.state;
|
||||
const nodes = appState.nodes.sorted();
|
||||
|
||||
return (
|
||||
<div className="Map">
|
||||
{
|
||||
nodes.map((node) => {
|
||||
const { lat, lon } = node;
|
||||
const focused = filter == null || filter(node);
|
||||
<React.Fragment>
|
||||
<div className="Map">
|
||||
{
|
||||
nodes.map((node) => {
|
||||
const { lat, lon } = node;
|
||||
|
||||
if (lat == null || lon == null) {
|
||||
// Skip nodes with unknown location
|
||||
return null;
|
||||
}
|
||||
const focused = filter == null || filter(node);
|
||||
|
||||
const position = this.pixelPosition(lat, lon);
|
||||
if (lat == null || lon == null) {
|
||||
// Skip nodes with unknown location
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Location key={node.id} position={position} focused={focused} node={node} />
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
const position = this.pixelPosition(lat, lon);
|
||||
|
||||
return (
|
||||
<Location key={node.id} position={position} focused={focused} node={node} />
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<Filter onChange={this.onFilterChange} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,4 +122,8 @@ export class Map extends React.Component<Map.Props, Map.State> {
|
||||
|
||||
this.setState({ top, left, width, height });
|
||||
}
|
||||
|
||||
private onFilterChange = (filter: Maybe<(node: Node) => boolean>) => {
|
||||
this.setState({ filter });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,3 +9,4 @@ export * from './Ago';
|
||||
export * from './OfflineIndicator';
|
||||
export * from './Sparkline';
|
||||
export * from './Tooltip';
|
||||
export * from './Filter';
|
||||
|
||||
Reference in New Issue
Block a user