From 97b3d1ec23c2dd618f61df0f62425ba14b480ad8 Mon Sep 17 00:00:00 2001 From: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com> Date: Fri, 12 Oct 2018 17:34:49 +0200 Subject: [PATCH] New List, Map, and Settings components (#86) --- .../frontend/src/components/Chain/Chain.css | 50 ----- .../frontend/src/components/Chain/Chain.tsx | 179 ++---------------- .../frontend/src/components/List/List.css | 66 +++++++ .../frontend/src/components/List/List.tsx | 54 ++++++ .../src/components/{Node => List}/Row.css | 26 +-- .../src/components/{Node => List}/Row.tsx | 38 ++-- .../components/{Node => List}/Truncate.tsx | 10 +- .../frontend/src/components/List/index.ts | 3 + .../src/components/{Node => Map}/Location.css | 32 ++-- .../src/components/{Node => Map}/Location.tsx | 20 +- packages/frontend/src/components/Map/Map.css | 11 ++ packages/frontend/src/components/Map/Map.tsx | 118 ++++++++++++ packages/frontend/src/components/Map/index.ts | 2 + .../frontend/src/components/Node/index.ts | 7 - .../src/components/{ => Settings}/Setting.css | 0 .../src/components/{ => Settings}/Setting.tsx | 6 +- .../src/components/Settings/Settings.css | 17 ++ .../src/components/Settings/Settings.tsx | 45 +++++ .../frontend/src/components/Settings/index.ts | 2 + packages/frontend/src/components/index.ts | 8 +- 20 files changed, 405 insertions(+), 289 deletions(-) create mode 100644 packages/frontend/src/components/List/List.css create mode 100644 packages/frontend/src/components/List/List.tsx rename packages/frontend/src/components/{Node => List}/Row.css (64%) rename packages/frontend/src/components/{Node => List}/Row.tsx (91%) rename packages/frontend/src/components/{Node => List}/Truncate.tsx (73%) create mode 100644 packages/frontend/src/components/List/index.ts rename packages/frontend/src/components/{Node => Map}/Location.css (74%) rename packages/frontend/src/components/{Node => Map}/Location.tsx (86%) create mode 100644 packages/frontend/src/components/Map/Map.css create mode 100644 packages/frontend/src/components/Map/Map.tsx create mode 100644 packages/frontend/src/components/Map/index.ts delete mode 100644 packages/frontend/src/components/Node/index.ts rename packages/frontend/src/components/{ => Settings}/Setting.css (100%) rename packages/frontend/src/components/{ => Settings}/Setting.tsx (88%) create mode 100644 packages/frontend/src/components/Settings/Settings.css create mode 100644 packages/frontend/src/components/Settings/Settings.tsx create mode 100644 packages/frontend/src/components/Settings/index.ts diff --git a/packages/frontend/src/components/Chain/Chain.css b/packages/frontend/src/components/Chain/Chain.css index 6e91f0e..36857b5 100644 --- a/packages/frontend/src/components/Chain/Chain.css +++ b/packages/frontend/src/components/Chain/Chain.css @@ -32,53 +32,3 @@ color: #fff; box-shadow: rgba(0,0,0,0.5) 0 3px 30px; } - -.Chain-map { - min-width: 1350px; - background: url('../../assets/world-map.svg') no-repeat; - background-size: contain; - background-position: center; - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; -} - -.Chain-no-nodes { - font-size: 30px; - padding-top: 20vh; - text-align: center; - font-weight: 300; -} - -.Chain-node-list { - width: 100%; - border-spacing: 0; -} - -.Chain-node-list thead { - background: #3c3c3b; -} - -.Chain-node-list tbody { - font-family: monospace, sans-serif; -} - -.Chain-settings { - text-align: center; -} - -.Chain-settings-category { - text-align: left; - width: 500px; - margin: 0 auto; - padding: 2em 0; -} - -.Chain-settings-category h2 { - padding: 0; - margin: 0 0 0.5em 0; - font-size: 20px; - font-weight: 100; -} diff --git a/packages/frontend/src/components/Chain/Chain.tsx b/packages/frontend/src/components/Chain/Chain.tsx index e5b1c85..27c8fcc 100644 --- a/packages/frontend/src/components/Chain/Chain.tsx +++ b/packages/frontend/src/components/Chain/Chain.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import { Types, Maybe } from '@dotstats/common'; import { State as AppState, Node as NodeState } from '../../state'; -import { formatNumber, secondsWithPrecision, viewport, getHashData } from '../../utils'; +import { formatNumber, secondsWithPrecision, getHashData } from '../../utils'; import { Tab, Filter } from './'; -import { Tile, Node, Ago, Setting } from '../'; +import { Tile, Ago, List, Map, Settings } from '../'; import { PersistentObject, PersistentSet } from '../../persist'; import blockIcon from '../../icons/package.svg'; @@ -13,12 +13,6 @@ import listIcon from '../../icons/list-alt-regular.svg'; import worldIcon from '../../icons/location.svg'; import settingsIcon from '../../icons/settings.svg'; -const MAP_RATIO = 800 / 350; -const MAP_HEIGHT_ADJUST = 400 / 350; -const HEADER = 148; -const TH_HEIGHT = 35; -const TR_HEIGHT = 31; - const ESCAPE_KEY = 27; import './Chain.css'; @@ -35,12 +29,6 @@ export namespace Chain { export interface State { display: Display; filter: Maybe; - map: { - width: number; - height: number; - top: number; - left: number; - } } } @@ -62,30 +50,21 @@ export class Chain extends React.Component { this.state = { display, filter: null, - map: { - width: 0, - height: 0, - top: 0, - left: 0 - } }; } public componentWillMount() { - this.onResize(); - - window.addEventListener('resize', this.onResize); window.addEventListener('keyup', this.onKeyUp); } public componentWillUnmount() { - window.removeEventListener('resize', this.onResize); window.removeEventListener('keyup', this.onKeyUp); } public render() { - const { best, blockTimestamp, blockAverage } = this.props.appState; - const currentTab = this.state.display; + const { appState } = this.props; + const { best, blockTimestamp, blockAverage } = appState; + const { display: currentTab } = this.state; return (
@@ -101,159 +80,37 @@ export class Chain extends React.Component {
- { - currentTab === 'list' - ? this.renderList() - : currentTab === 'map' - ? this.renderMap() - : this.renderSettings() - } + {this.renderContent()}
); } - private setDisplay = (display: Chain.Display) => { - this.setState({ display }); - }; + private renderContent() { + const { display, filter } = this.state; - private renderList() { - const { settings } = this.props.appState; - const { pins } = this.props; - const { filter } = this.state; - const nodeFilter = this.getNodeFilter(); - const nodes = nodeFilter ? this.nodes().filter(nodeFilter) : this.nodes(); - const columns = Node.Row.columns.filter(({ setting }) => setting == null || settings[setting]); - - if (nodeFilter && nodes.length === 0) { - return ( - - -
¯\_(ツ)_/¯
Nothing matches
-
- ); + if (display === 'settings') { + return ; } - const height = TH_HEIGHT + nodes.length * TR_HEIGHT; - - return ( -
- - - - - { - nodes.map((node) => ) - } - -
-
- ); - } - - private renderMap() { - const { filter } = this.state; - const nodeFilter = this.getNodeFilter(); + const { appState, pins } = this.props; return ( - {filter != null ? : null} -
+ { - this.nodes().map((node) => { - const { lat, lon } = node; - const focused = nodeFilter == null || nodeFilter(node); - - if (lat == null || lon == null) { - // Skip nodes with unknown location - return null; - } - - const position = this.pixelPosition(lat, lon); - - return ( - - ); - }) + display === 'list' + ? + : } -
); } - private renderSettings() { - const { settings } = this.props; - - return ( -
-
-

Visible Columns

- { - Node.Row.columns - .map(({ label, icon, setting }, index) => { - if (!setting) { - return null; - } - - return ; - }) - } -
-
- ); - } - - private nodes(): NodeState[] { - return this.props.appState.nodes.sorted(); - } - - private pixelPosition(lat: Types.Latitude, lon: Types.Longitude): Node.Location.Position { - const { map } = this.state; - - // Longitude ranges -180 (west) to +180 (east) - // Latitude ranges +90 (north) to -90 (south) - const left = Math.round(((180 + lon) / 360) * map.width + map.left); - const top = Math.round(((90 - lat) / 180) * map.height + map.top) * MAP_HEIGHT_ADJUST; - - let quarter: Node.Location.Quarter = 0; - - if (lon > 0) { - quarter = (quarter | 1) as Node.Location.Quarter; - } - - if (lat < 0) { - quarter = (quarter | 2) as Node.Location.Quarter; - } - - return { left, top, quarter }; - } - - private onResize: () => void = () => { - const vp = viewport(); - - vp.width = Math.max(1350, vp.width); - vp.height -= HEADER; - - const ratio = vp.width / vp.height; - - let top = 0; - let left = 0; - let width = 0; - let height = 0; - - if (ratio >= MAP_RATIO) { - width = Math.round(vp.height * MAP_RATIO); - height = Math.round(vp.height); - left = (vp.width - width) / 2; - } else { - width = Math.round(vp.width); - height = Math.round(vp.width / MAP_RATIO); - top = (vp.height - height) / 2; - } - - this.setState({ map: { top, left, width, height }}); - } + private setDisplay = (display: Chain.Display) => { + this.setState({ display }); + }; private onKeyUp = (event: KeyboardEvent) => { if (event.ctrlKey) { diff --git a/packages/frontend/src/components/List/List.css b/packages/frontend/src/components/List/List.css new file mode 100644 index 0000000..18f0269 --- /dev/null +++ b/packages/frontend/src/components/List/List.css @@ -0,0 +1,66 @@ +.Chain-header { + width: 100%; + height: 108px; + overflow: hidden; + background: #fff; + color: #000; + min-width: 1350px; + position: relative; +} + +.Chain-tabs { + position: absolute; + right: 5px; + bottom: 10px; + width: 200px; + text-align: right; +} + +.Chain-content-container { + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 148px; +} + +.Chain-content { + width: 100%; + min-width: 1350px; + min-height: 100%; + background: #222; + color: #fff; + box-shadow: rgba(0,0,0,0.5) 0 3px 30px; +} + +.Chain-map { + min-width: 1350px; + background: url('../../assets/world-map.svg') no-repeat; + background-size: contain; + background-position: center; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} + +.List-no-nodes { + font-size: 30px; + padding-top: 20vh; + text-align: center; + font-weight: 300; +} + +.List table { + width: 100%; + border-spacing: 0; +} + +.List thead { + background: #3c3c3b; +} + +.List tbody { + font-family: monospace, sans-serif; +} diff --git a/packages/frontend/src/components/List/List.tsx b/packages/frontend/src/components/List/List.tsx new file mode 100644 index 0000000..5f89ccf --- /dev/null +++ b/packages/frontend/src/components/List/List.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; +import { Types, Maybe } from '@dotstats/common'; +import { State as AppState, Node } from '../../state'; +import { Row } from './'; +import { PersistentSet } from '../../persist'; + +// const HEADER = 148; +const TH_HEIGHT = 35; +const TR_HEIGHT = 31; + +import './List.css'; + +export namespace List { + export interface Props { + filter: Maybe<(node: Node) => boolean>; + appState: Readonly; + pins: PersistentSet; + } +} + +export class List extends React.Component { + public render() { + const { settings } = this.props.appState; + const { pins, filter } = this.props; + const columns = Row.columns.filter(({ setting }) => setting == null || settings[setting]); + + let nodes = this.props.appState.nodes.sorted(); + + if (filter != null) { + nodes = nodes.filter(filter); + + if (nodes.length === 0) { + return ( +
¯\_(ツ)_/¯
Nothing matches
+ ); + } + } + + const height = TH_HEIGHT + nodes.length * TR_HEIGHT; + + return ( +
+ + + + { + nodes.map((node) => ) + } + +
+
+ ); + } +} diff --git a/packages/frontend/src/components/Node/Row.css b/packages/frontend/src/components/List/Row.css similarity index 64% rename from packages/frontend/src/components/Node/Row.css rename to packages/frontend/src/components/List/Row.css index ea38c84..1a3ed4a 100644 --- a/packages/frontend/src/components/Node/Row.css +++ b/packages/frontend/src/components/List/Row.css @@ -1,23 +1,23 @@ -.Node-Row { +.Row { color: #999; cursor: pointer; } -.Node-Row-Header th, .Node-Row td { +.Row-Header th, .Row td { text-align: left; padding: 6px 13px; height: 19px; } -.Node-Row td { +.Row td { position: relative; } -.Node-Row-Header th { +.Row-Header th { height: 23px; } -.Node-Row .Node-Row-truncate { +.Row .Row-truncate { position: absolute; left: 0; right: 0; @@ -28,40 +28,40 @@ text-overflow: ellipsis; } -.Node-Row .Node-Row-Tooltip { +.Row .Row-Tooltip { position: initial; padding: inherit; } -.Node-Row-synced { +.Row-synced { color: #fff; } -.Node-Row-pinned td:first-child { +.Row-pinned td:first-child { border-left: 3px solid #d64ca8; padding-left: 10px; } -.Node-Row-pinned td:last-child { +.Row-pinned td:last-child { border-right: 3px solid #d64ca8; padding-right: 10px; } -.Node-Row-pinned.Node-Row-synced { +.Row-pinned.Row-synced { color: #d64ca8; } -.Node-Row:hover { +.Row:hover { background-color: #161616; } -.Node-Row-validator { +.Row-validator { display: block; width: 16px; height: 16px; cursor: pointer; } -.Node-Row-validator:hover { +.Row-validator:hover { transform: scale(2); } diff --git a/packages/frontend/src/components/Node/Row.tsx b/packages/frontend/src/components/List/Row.tsx similarity index 91% rename from packages/frontend/src/components/Node/Row.tsx rename to packages/frontend/src/components/List/Row.tsx index eb9c2ed..cba1db9 100644 --- a/packages/frontend/src/components/Node/Row.tsx +++ b/packages/frontend/src/components/List/Row.tsx @@ -4,7 +4,7 @@ import { Types, Maybe, timestamp } from '@dotstats/common'; import { formatNumber, milliOrSecond, secondsWithPrecision } from '../../utils'; import { State as AppState, Node } from '../../state'; import { PersistentSet } from '../../persist'; -import { SEMVER_PATTERN, Truncate } from './'; +import { Truncate } from './'; import { Ago, Icon, Tooltip, Sparkline } from '../'; import nodeIcon from '../../icons/server.svg'; @@ -27,19 +27,23 @@ import unknownImplementationIcon from '../../icons/question-solid.svg'; import './Row.css'; -interface RowProps { - node: Node; - pins: PersistentSet; - columns: Column[]; -}; +const SEMVER_PATTERN = /^\d+\.\d+\.\d+/; -interface RowState { - update: number; -}; +export namespace Row { + export interface Props { + node: Node; + pins: PersistentSet; + columns: Column[]; + } + + export interface State { + update: number; + } +} interface HeaderProps { columns: Column[]; -}; +} interface Column { label: string; @@ -82,7 +86,7 @@ function formatCPU(cpu: number, stamp: Maybe): string { return `${cpu.toFixed(fractionDigits)}%${ago}`; } -export default class Row extends React.Component { +export class Row extends React.Component { public static readonly columns: Column[] = [ { label: 'Node', @@ -95,7 +99,7 @@ export default class Row extends React.Component { width: 16, setting: 'validator', render: ({ validator }) => { - return validator ? : '-'; + return validator ? : '-'; } }, { @@ -210,7 +214,7 @@ export default class Row extends React.Component { return ( - + { columns.map(({ icon, width, label }, index) => { const position = index === 0 ? 'left' @@ -243,21 +247,21 @@ export default class Row extends React.Component { node.unsubscribe(this.onUpdate); } - public shouldComponentUpdate(nextProps: RowProps, nextState: RowState): boolean { + public shouldComponentUpdate(nextProps: Row.Props, nextState: Row.State): boolean { return this.props.node.id !== nextProps.node.id || this.state.update !== nextState.update; } public render() { const { node, columns } = this.props; - let className = 'Node-Row'; + let className = 'Row'; if (node.propagationTime != null) { - className += ' Node-Row-synced'; + className += ' Row-synced'; } if (node.pinned) { - className += ' Node-Row-pinned'; + className += ' Row-pinned'; } return ( diff --git a/packages/frontend/src/components/Node/Truncate.tsx b/packages/frontend/src/components/List/Truncate.tsx similarity index 73% rename from packages/frontend/src/components/Node/Truncate.tsx rename to packages/frontend/src/components/List/Truncate.tsx index a57a051..fc44dd4 100644 --- a/packages/frontend/src/components/Node/Truncate.tsx +++ b/packages/frontend/src/components/List/Truncate.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Tooltip } from '../'; -namespace Truncate { +export namespace Truncate { export interface Props { text: string; copy?: boolean; @@ -9,13 +9,13 @@ namespace Truncate { } } -class Truncate extends React.Component { +export class Truncate extends React.Component { public render() { const { text, position, copy } = this.props; return ( - -
{text}
+ +
{text}
); } @@ -24,5 +24,3 @@ class Truncate extends React.Component { return this.props.text !== nextProps.text || this.props.position !== nextProps.position; } } - -export default Truncate; diff --git a/packages/frontend/src/components/List/index.ts b/packages/frontend/src/components/List/index.ts new file mode 100644 index 0000000..82a4d89 --- /dev/null +++ b/packages/frontend/src/components/List/index.ts @@ -0,0 +1,3 @@ +export * from './List'; +export * from './Truncate'; +export * from './Row'; diff --git a/packages/frontend/src/components/Node/Location.css b/packages/frontend/src/components/Map/Location.css similarity index 74% rename from packages/frontend/src/components/Node/Location.css rename to packages/frontend/src/components/Map/Location.css index d9193aa..4c250fa 100644 --- a/packages/frontend/src/components/Node/Location.css +++ b/packages/frontend/src/components/Map/Location.css @@ -1,5 +1,5 @@ -.Node-Location { +.Location { width: 6px; height: 6px; background: transparent; @@ -15,7 +15,7 @@ transition: border-color 0.25s linear; } -.Node-Location-dimmed { +.Location-dimmed { width: 2px; height: 2px; margin-left: -1px; @@ -25,34 +25,34 @@ border: none; } -.Node-Location-ping { +.Location-ping { pointer-events: none; position: absolute; display: none; } -.Node-Location-odd { +.Location-odd { border-color: #bbb; } -.Node-Location-synced { +.Location-synced { z-index: 3; border-color: #d64ca8; } -.Node-Location-synced .Node-Location-ping { +.Location-synced .Location-ping { border: 1px solid #fff; border-radius: 30px; display: block; animation: ping 1s forwards; } -.Node-Location:hover { +.Location:hover { z-index: 4; border-color: #fff; } -.Node-Location-details { +.Location-details { min-width: 335px; position: absolute; font-family: monospace, sans-serif; @@ -62,38 +62,38 @@ border-collapse: collapse; } -.Node-Location-quarter0 .Node-Location-details { +.Location-quarter0 .Location-details { left: 16px; top: -4px; } -.Node-Location-quarter1 .Node-Location-details { +.Location-quarter1 .Location-details { right: 16px; top: -4px; } -.Node-Location-quarter2 .Node-Location-details { +.Location-quarter2 .Location-details { left: 16px; bottom: -4px; } -.Node-Location-quarter3 .Node-Location-details { +.Location-quarter3 .Location-details { right: 16px; bottom: -4px; } -.Node-Location-details td { +.Location-details td { text-align: left; padding: 0.5em 1em; } -.Node-Location-details td:nth-child(odd) { +.Location-details td:nth-child(odd) { width: 16px; text-align: center; padding-right: 0.2em; } -.Node-Location-details td:nth-child(even) { +.Location-details td:nth-child(even) { padding-left: 0.2em; } @@ -117,7 +117,7 @@ } } -.Node-Location-validator { +.Location-validator { display: inline-block; width: 16px; height: 16px; diff --git a/packages/frontend/src/components/Node/Location.tsx b/packages/frontend/src/components/Map/Location.tsx similarity index 86% rename from packages/frontend/src/components/Node/Location.tsx rename to packages/frontend/src/components/Map/Location.tsx index 85006fe..964e6eb 100644 --- a/packages/frontend/src/components/Node/Location.tsx +++ b/packages/frontend/src/components/Map/Location.tsx @@ -16,7 +16,7 @@ import lastTimeIcon from '../../icons/watch.svg'; import './Location.css'; -namespace Location { +export namespace Location { export type Quarter = 0 | 1 | 2 | 3; export interface Props { @@ -36,7 +36,7 @@ namespace Location { } } -class Location extends React.Component { +export class Location extends React.Component { public readonly state = { hover: false }; public render() { @@ -48,16 +48,16 @@ class Location extends React.Component { return null; } - let className = `Node-Location Node-Location-quarter${quarter}`; + let className = `Location Location-quarter${quarter}`; if (focused) { if (propagationTime != null) { - className += ' Node-Location-synced'; + className += ' Location-synced'; } else if (height % 2 === 1) { - className += ' Node-Location-odd'; + className += ' Location-odd'; } } else { - className += ' Node-Location-dimmed'; + className += ' Location-dimmed'; } return ( @@ -65,7 +65,7 @@ class Location extends React.Component { { this.state.hover ? this.renderDetails() : null } -
+
); } @@ -92,14 +92,14 @@ class Location extends React.Component { {trimHash(validator, 30)} - + ); } return ( - +
@@ -138,5 +138,3 @@ class Location extends React.Component { this.setState({ hover: false }); } } - -export default Location; diff --git a/packages/frontend/src/components/Map/Map.css b/packages/frontend/src/components/Map/Map.css new file mode 100644 index 0000000..0f8b8ef --- /dev/null +++ b/packages/frontend/src/components/Map/Map.css @@ -0,0 +1,11 @@ +.Map { + min-width: 1350px; + background: url('../../assets/world-map.svg') no-repeat; + background-size: contain; + background-position: center; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} diff --git a/packages/frontend/src/components/Map/Map.tsx b/packages/frontend/src/components/Map/Map.tsx new file mode 100644 index 0000000..7838cd1 --- /dev/null +++ b/packages/frontend/src/components/Map/Map.tsx @@ -0,0 +1,118 @@ +import * as React from 'react'; +import { Types, Maybe } from '@dotstats/common'; +import { State as AppState, Node } from '../../state'; +import { Location } from './'; +import { viewport } from '../../utils'; + +const MAP_RATIO = 800 / 350; +const MAP_HEIGHT_ADJUST = 400 / 350; +const HEADER = 148; + +import './Map.css'; + +export namespace Map { + export interface Props { + filter: Maybe<(node: Node) => boolean>; + appState: Readonly; + } + + export interface State { + width: number; + height: number; + top: number; + left: number; + } +} + +export class Map extends React.Component { + public state = { + width: 0, + height: 0, + top: 0, + left: 0 + } + + public componentWillMount() { + this.onResize(); + + window.addEventListener('resize', this.onResize); + } + + public componentWillUnmount() { + window.removeEventListener('resize', this.onResize); + } + + public render() { + const { filter, appState } = this.props; + const nodes = appState.nodes.sorted(); + + return ( +
+ { + nodes.map((node) => { + const { lat, lon } = node; + const focused = filter == null || filter(node); + + if (lat == null || lon == null) { + // Skip nodes with unknown location + return null; + } + + const position = this.pixelPosition(lat, lon); + + return ( + + ); + }) + } +
+ ); + } + + private pixelPosition(lat: Types.Latitude, lon: Types.Longitude): Location.Position { + const { state } = this; + + // Longitude ranges -180 (west) to +180 (east) + // Latitude ranges +90 (north) to -90 (south) + const left = Math.round(((180 + lon) / 360) * state.width + state.left); + const top = Math.round(((90 - lat) / 180) * state.height + state.top) * MAP_HEIGHT_ADJUST; + + let quarter: Location.Quarter = 0; + + if (lon > 0) { + quarter = (quarter | 1) as Location.Quarter; + } + + if (lat < 0) { + quarter = (quarter | 2) as Location.Quarter; + } + + return { left, top, quarter }; + } + + private onResize: () => void = () => { + const vp = viewport(); + + vp.width = Math.max(1350, vp.width); + vp.height -= HEADER; + + const ratio = vp.width / vp.height; + + let top = 0; + let left = 0; + let width = 0; + let height = 0; + + if (ratio >= MAP_RATIO) { + width = Math.round(vp.height * MAP_RATIO); + height = Math.round(vp.height); + left = (vp.width - width) / 2; + } else { + width = Math.round(vp.width); + height = Math.round(vp.width / MAP_RATIO); + top = (vp.height - height) / 2; + } + + this.setState({ top, left, width, height }); + } +} diff --git a/packages/frontend/src/components/Map/index.ts b/packages/frontend/src/components/Map/index.ts new file mode 100644 index 0000000..602baea --- /dev/null +++ b/packages/frontend/src/components/Map/index.ts @@ -0,0 +1,2 @@ +export * from './Map'; +export * from './Location'; diff --git a/packages/frontend/src/components/Node/index.ts b/packages/frontend/src/components/Node/index.ts deleted file mode 100644 index aeb8364..0000000 --- a/packages/frontend/src/components/Node/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import Row from './Row'; -import Location from './Location'; -import Truncate from './Truncate'; - -export { Row, Location, Truncate }; - -export const SEMVER_PATTERN = /^\d+\.\d+\.\d+/; diff --git a/packages/frontend/src/components/Setting.css b/packages/frontend/src/components/Settings/Setting.css similarity index 100% rename from packages/frontend/src/components/Setting.css rename to packages/frontend/src/components/Settings/Setting.css diff --git a/packages/frontend/src/components/Setting.tsx b/packages/frontend/src/components/Settings/Setting.tsx similarity index 88% rename from packages/frontend/src/components/Setting.tsx rename to packages/frontend/src/components/Settings/Setting.tsx index 668c622..81d3fd1 100644 --- a/packages/frontend/src/components/Setting.tsx +++ b/packages/frontend/src/components/Settings/Setting.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; -import { Icon } from './'; -import { State } from '../state'; -import { PersistentObject } from '../persist'; +import { Icon } from '../'; +import { State } from '../../state'; +import { PersistentObject } from '../../persist'; import './Setting.css'; diff --git a/packages/frontend/src/components/Settings/Settings.css b/packages/frontend/src/components/Settings/Settings.css new file mode 100644 index 0000000..2be4b57 --- /dev/null +++ b/packages/frontend/src/components/Settings/Settings.css @@ -0,0 +1,17 @@ +.Settings { + text-align: center; +} + +.Settings-category { + text-align: left; + width: 500px; + margin: 0 auto; + padding: 2em 0; +} + +.Settings-category h2 { + padding: 0; + margin: 0 0 0.5em 0; + font-size: 20px; + font-weight: 100; +} diff --git a/packages/frontend/src/components/Settings/Settings.tsx b/packages/frontend/src/components/Settings/Settings.tsx new file mode 100644 index 0000000..2edc775 --- /dev/null +++ b/packages/frontend/src/components/Settings/Settings.tsx @@ -0,0 +1,45 @@ +import * as React from 'react'; +import { Maybe } from '@dotstats/common'; +import { State as AppState } 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; + } + + export interface State { + display: Display; + filter: Maybe; + } +} + +export class Settings extends React.Component { + public render() { + const { settings } = this.props; + + return ( +
+
+

Visible Columns

+ { + Row.columns + .map(({ label, icon, setting }, index) => { + if (!setting) { + return null; + } + + return ; + }) + } +
+
+ ); + } +} diff --git a/packages/frontend/src/components/Settings/index.ts b/packages/frontend/src/components/Settings/index.ts new file mode 100644 index 0000000..93d8026 --- /dev/null +++ b/packages/frontend/src/components/Settings/index.ts @@ -0,0 +1,2 @@ +export * from './Settings'; +export * from './Setting'; diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 2f2cffc..bdb25c5 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -1,13 +1,11 @@ export * from './Chains'; export * from './Chain'; +export * from './List'; +export * from './Map'; +export * from './Settings'; export * from './Icon'; export * from './Tile'; export * from './Ago'; export * from './OfflineIndicator'; -export * from './Setting'; export * from './Sparkline'; export * from './Tooltip'; - -import * as Node from './Node'; - -export { Node };
{name}