Browse Source

fix(peers): peers makeover

renovate/lint-staged-8.x
Jack Mallers 7 years ago
parent
commit
b492fc6bd7
  1. 133
      app/components/Channels/NetworkChannels.js
  2. 114
      app/components/Channels/NetworkChannels.scss
  3. 86
      app/components/Peers/Peers.js
  4. 59
      app/components/Peers/Peers.scss
  5. 2
      app/components/Wallet/ReceiveModal.js
  6. 2
      app/main.dev.js
  7. 6
      app/routes/activity/components/Activity.js
  8. 4
      app/routes/activity/containers/ActivityContainer.js
  9. 6
      app/routes/app/components/App.js
  10. 104
      app/routes/channels/components/Channels.js
  11. 2
      app/routes/channels/components/Channels.scss
  12. 4
      app/routes/network/components/Network.js
  13. 68
      app/routes/peers/components/Peers.js
  14. 55
      app/routes/peers/components/Peers.scss

133
app/components/Channels/NetworkChannels.js

@ -1,133 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { ForceGraph, ForceGraphNode, ForceGraphLink } from 'react-vis-force'
import { FaCircle } from 'react-icons/lib/fa'
import styles from './NetworkChannels.scss'
class NetworkChannels extends Component {
constructor(props) {
super(props)
this.state = {
ready: false
}
}
componentWillMount() {
setTimeout(() => {
this.setState({ ready: true })
}, 1000)
this.props.setCurrentChannel(this.props.channels[0])
}
render() {
const { ready } = this.state
const {
channels,
network: { nodes, edges, selectedChannel, networkLoading },
identity_pubkey,
setCurrentChannel
} = this.props
if (!ready || networkLoading) {
return (
<div className={styles.networkLoading}>
<h1>loading network graph</h1>
</div>
)
}
return (
<div className={styles.networkchannels}>
<div className={styles.network}>
<ForceGraph
simulationOptions={
{
width: 1000,
height: 1000,
strength: {
charge: -750
}
}
}
labelAttr='label'
opacityFactor={1}
zoomOptions={{ minScale: 0.1, maxScale: 5 }}
zoom
animate
highlightDependencies
>
{
nodes.map(node => (
<ForceGraphNode
r={15}
label={node.pub_key}
key={node.pub_key}
node={{ id: node.pub_key }}
className={`${styles.node} ${identity_pubkey === node.pub_key && styles.active}`}
fill={identity_pubkey === node.pub_key ? 'green' : 'silver'}
/>
))
}
{
edges.map(edge => (
<ForceGraphLink
className={`${styles.line} ${selectedChannel.chan_id === edge.channel_id && styles.active}`}
key={edge.channel_id}
link={{ source: edge.node1_pub, target: edge.node2_pub }}
/>
))
}
</ForceGraph>
</div>
<div className={styles.channels}>
<ul>
{
channels.map((channel, index) => (
<li
key={index}
className={`${styles.channel} ${channel.chan_id === selectedChannel.chan_id && styles.active}`}
onClick={() => setCurrentChannel(channel)}
>
<header>
{
channel.active ?
<span className={styles.active}>
<FaCircle />
<i>active</i>
</span>
:
<span className={styles.notactive}>
<FaCircle />
<i>not active</i>
</span>
}
</header>
<div className={styles.content}>
<div>
<h4>Remote Pubkey</h4>
<h2>{`${channel.remote_pubkey.substring(30, channel.remote_pubkey.length)}...`}</h2>
</div>
<div>
<h4>Channel Point</h4>
<h2>{`${channel.channel_point.substring(30, channel.channel_point.length)}...`}</h2>
</div>
</div>
</li>
))
}
</ul>
</div>
</div>
)
}
}
NetworkChannels.propTypes = {
channels: PropTypes.array.isRequired,
network: PropTypes.object.isRequired,
identity_pubkey: PropTypes.string.isRequired,
setCurrentChannel: PropTypes.func.isRequired
}
export default NetworkChannels

114
app/components/Channels/NetworkChannels.scss

@ -1,114 +0,0 @@
@import '../../variables.scss';
@keyframes dash {
to {
stroke-dashoffset: 1000;
}
}
.networkLoading {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background: $black;
h1 {
font-size: 22px;
color: $green;
font-family: 'courier';
text-align: center;
margin-top: 25%;
}
}
.networkchannels {
position: relative;
}
.network, .channels {
display: inline-block;
vertical-align: top;
}
.network {
width: 70%;
}
.channels {
width: calc(30% - 2px);
border: 1px solid $darkestgrey;
border-radius: 5px;
}
.node {
r: 15;
fill: $darkestgrey;
.active {
r: 25;
fill: $green;
stroke: $green;
}
}
.line.active {
stroke-width: 10;
opacity: 1;
stroke: $green;
stroke-dasharray: 100;
animation: dash 2.5s infinite linear;
}
.channel {
padding: 10px;
background: rgba(0, 0, 0, 0.7);
cursor: pointer;
transition: all 0.25s;
border-bottom: 1px solid $white;
&.active, &:hover {
background: rgba(255, 255, 255, 0.4);
}
header {
margin-bottom: 10px;
.active {
color: $green;
}
.notactive {
color: $green;
}
span svg {
font-size: 10px;
}
i {
text-transform: uppercase;
font-size: 10px;
margin-left: 5px;
}
}
.content {
div {
margin-bottom: 5px;
}
h4 {
text-transform: uppercase;
margin-bottom: 5px;
font-size: 10px;
color: $main;
}
h2 {
font-size: 14px;
color: $white;
}
}
}

86
app/components/Peers/Peers.js

@ -1,86 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { TiPlus } from 'react-icons/lib/ti'
import { FaRepeat } from 'react-icons/lib/fa'
import PeerModal from './PeerModal'
import PeerForm from './PeerForm'
import Peer from './Peer'
import styles from './Peers.scss'
const Peers = ({
fetchPeers,
peersLoading,
peers,
setPeer,
modalPeer,
peerModalOpen,
peerForm,
setPeerForm,
connect,
disconnect
}) => {
const refreshClicked = (event) => {
// store event in icon so we dont get an error when react clears it
const icon = event.currentTarget
// fetch peers
fetchPeers()
// clear animation after the second so we can reuse it
setTimeout(() => { icon.style.animation = '' }, 1000)
// spin icon for 1 sec
icon.style.animation = 'spin 1000ms linear 1'
}
return (
<div className={styles.peers}>
<PeerModal isOpen={peerModalOpen} resetPeer={setPeer} peer={modalPeer} disconnect={disconnect} />
<PeerForm form={peerForm} setForm={setPeerForm} connect={connect} />
<div className={styles.header}>
<h3>Peers</h3>
<span
className={`${styles.refresh} hint--top`}
data-hint='Refresh your peers list'
>
<FaRepeat
style={{ verticalAlign: 'baseline' }}
onClick={refreshClicked}
/>
</span>
<div
className={`${styles.connectPeer} hint--top`}
data-hint='Connect to a peer'
onClick={() => setPeerForm({ isOpen: true })}
>
<TiPlus />
</div>
</div>
<ul>
{
!peersLoading ?
peers.map(peer => <Peer key={peer.peer_id} peer={peer} setPeer={setPeer} />)
:
'Loading...'
}
</ul>
</div>
)
}
Peers.propTypes = {
fetchPeers: PropTypes.func.isRequired,
peersLoading: PropTypes.bool.isRequired,
peers: PropTypes.array.isRequired,
setPeer: PropTypes.func.isRequired,
modalPeer: PropTypes.object,
peerModalOpen: PropTypes.bool.isRequired,
peerForm: PropTypes.object.isRequired,
setPeerForm: PropTypes.func.isRequired,
connect: PropTypes.func.isRequired,
disconnect: PropTypes.func.isRequired
}
export default Peers

59
app/components/Peers/Peers.scss

@ -1,59 +0,0 @@
@import '../../variables.scss';
.peers {
width: 75%;
margin: 50px auto;
.header {
margin-bottom: 10px;
h3, .connectPeer {
display: inline-block;
}
h3 {
text-align: left;
}
.refresh {
cursor: pointer;
margin-left: 5px;
font-size: 12px;
vertical-align: top;
color: $darkestgrey;
line-height: 14px;
transition: color 0.25s;
&:hover {
color: $main;
}
}
.connectPeer {
float: right;
cursor: pointer;
svg {
padding: 3px;
border-radius: 50%;
border: 1px solid $main;
color: $main;
transition: all 0.25s;
&:hover {
border-color: darken($main, 10%);
color: darken($main, 10%);
}
}
}
}
h3 {
text-transform: uppercase;
color: $darkestgrey;
letter-spacing: 1.6px;
font-size: 14px;
font-weight: 400;
margin-bottom: 10px;
}
}

2
app/components/Wallet/ReceiveModal.js

@ -16,7 +16,7 @@ const ReceiveModal = ({ isOpen, hideActivityModal, pubkey, address }) => {
left: '20%', left: '20%',
right: '0', right: '0',
bottom: 'auto', bottom: 'auto',
width: '40%', width: '60%',
margin: '50px auto' margin: '50px auto'
} }
} }

2
app/main.dev.js

@ -165,7 +165,7 @@ export const startLnd = () => {
'--bitcoin.active', '--bitcoin.active',
'--bitcoin.testnet', '--bitcoin.testnet',
'--neutrino.active', '--neutrino.active',
'--neutrino.connect=165.227.7.29:18333', '--neutrino.connect=faucet.lightning.community:18333',
'--autopilot.active', '--autopilot.active',
'--debuglevel=debug', '--debuglevel=debug',
'--no-macaroons', '--no-macaroons',

6
app/routes/activity/components/Activity.js

@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import { MdSearch } from 'react-icons/lib/md' import { MdSearch } from 'react-icons/lib/md'
import { FaAngleDown } from 'react-icons/lib/fa' import { FaAngleDown } from 'react-icons/lib/fa'
import Wallet from 'components/Wallet'
import Invoice from './components/Invoice' import Invoice from './components/Invoice'
import Payment from './components/Payment' import Payment from './components/Payment'
import Transaction from './components/Transaction' import Transaction from './components/Transaction'
@ -44,6 +45,9 @@ class Activity extends Component {
ticker, ticker,
searchInvoices, searchInvoices,
invoice: { invoicesSearchText, invoiceLoading }, invoice: { invoicesSearchText, invoiceLoading },
address: { address },
balance,
info,
payment: { paymentLoading }, payment: { paymentLoading },
currentTicker, currentTicker,
activity: { modal, filter, filterPulldown }, activity: { modal, filter, filterPulldown },
@ -66,6 +70,8 @@ class Activity extends Component {
currentTicker={currentTicker} currentTicker={currentTicker}
/> />
<Wallet balance={balance} address={address} info={info} />
<div className={styles.search}> <div className={styles.search}>
<label className={`${styles.label} ${styles.input}`} htmlFor='invoiceSearch'> <label className={`${styles.label} ${styles.input}`} htmlFor='invoiceSearch'>
<MdSearch /> <MdSearch />

4
app/routes/activity/containers/ActivityContainer.js

@ -37,6 +37,10 @@ const mapDispatchToProps = {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
activity: state.activity, activity: state.activity,
balance: state.balance,
address: state.address,
info: state.info,
payment: state.payment, payment: state.payment,
invoice: state.invoice, invoice: state.invoice,

6
app/routes/app/components/App.js

@ -6,7 +6,6 @@ import LoadingBolt from 'components/LoadingBolt'
import Form from 'components/Form' import Form from 'components/Form'
import ModalRoot from 'components/ModalRoot' import ModalRoot from 'components/ModalRoot'
import Nav from 'components/Nav' import Nav from 'components/Nav'
import Wallet from 'components/Wallet'
import styles from './App.scss' import styles from './App.scss'
class App extends Component { class App extends Component {
@ -66,11 +65,6 @@ class App extends Component {
/> />
<div className={styles.content}> <div className={styles.content}>
<Wallet
balance={balance}
address={address}
info={info}
/>
{children} {children}
</div> </div>
</div> </div>

104
app/routes/channels/components/Channels.js

@ -7,18 +7,16 @@ import { MdSearch } from 'react-icons/lib/md'
import OpenPendingChannel from 'components/Channels/OpenPendingChannel' import OpenPendingChannel from 'components/Channels/OpenPendingChannel'
import ClosedPendingChannel from 'components/Channels/ClosedPendingChannel' import ClosedPendingChannel from 'components/Channels/ClosedPendingChannel'
import Channel from 'components/Channels/Channel' import Channel from 'components/Channels/Channel'
import NetworkChannels from 'components/Channels/NetworkChannels'
import ChannelForm from 'components/ChannelForm' import ChannelForm from 'components/ChannelForm'
import styles from './Channels.scss' import styles from './Channels.scss'
class Channels extends Component { class Channels extends Component {
componentWillMount() { componentWillMount() {
const { fetchChannels, fetchPeers, fetchDescribeNetwork } = this.props const { fetchChannels, fetchPeers } = this.props
fetchChannels() fetchChannels()
fetchPeers() fetchPeers()
fetchDescribeNetwork()
} }
render() { render() {
@ -47,18 +45,14 @@ class Channels extends Component {
ticker, ticker,
currentTicker, currentTicker,
channelFormProps, channelFormProps
network,
identity_pubkey,
setCurrentChannel
} = this.props } = this.props
const refreshClicked = (event) => { const refreshClicked = (event) => {
// store event in icon so we dont get an error when react clears it // store event in icon so we dont get an error when react clears it
const icon = event.currentTarget const icon = event.currentTarget
// fetch peers // fetch channels
fetchChannels() fetchChannels()
// clear animation after the second so we can reuse it // clear animation after the second so we can reuse it
@ -68,12 +62,6 @@ class Channels extends Component {
icon.style.animation = 'spin 1000ms linear 1' icon.style.animation = 'spin 1000ms linear 1'
} }
const networkClicked = () => {
if (!activeChannels.length) { return }
setViewType(1)
}
return ( return (
<div className={`${styles.container} ${viewType === 1 && styles.graphview}`}> <div className={`${styles.container} ${viewType === 1 && styles.graphview}`}>
<ChannelForm {...channelFormProps} /> <ChannelForm {...channelFormProps} />
@ -92,14 +80,6 @@ class Channels extends Component {
/> />
</div> </div>
<header className={styles.header}> <header className={styles.header}>
<div className={styles.layoutsContainer}>
<span className={viewType === 0 && styles.active} onClick={() => setViewType(0)}>
<FaAlignJustify />
</span>
<span className={viewType === 1 && styles.active} onClick={networkClicked}>
<FaGlobe />
</span>
</div>
<div className={styles.createChannelContainer}> <div className={styles.createChannelContainer}>
<div className={`buttonPrimary ${styles.newChannelButton}`} onClick={openChannelForm}> <div className={`buttonPrimary ${styles.newChannelButton}`} onClick={openChannelForm}>
Create new channel Create new channel
@ -131,53 +111,42 @@ class Channels extends Component {
</div> </div>
<div className={`${styles.channels} ${filterPulldown && styles.fade}`}> <div className={`${styles.channels} ${filterPulldown && styles.fade}`}>
{ <ul className={viewType === 1 && styles.cardsContainer}>
viewType === 0 && {
<ul className={viewType === 1 && styles.cardsContainer}> currentChannels.map((channel, index) => {
{ if (Object.prototype.hasOwnProperty.call(channel, 'blocks_till_open')) {
currentChannels.map((channel, index) => {
if (Object.prototype.hasOwnProperty.call(channel, 'blocks_till_open')) {
return (
<OpenPendingChannel
key={index}
channel={channel}
ticker={ticker}
currentTicker={currentTicker}
explorerLinkBase={'https://testnet.smartbit.com.au/'}
/>
)
} else if (Object.prototype.hasOwnProperty.call(channel, 'closing_txid')) {
return (
<ClosedPendingChannel
key={index}
channel={channel}
ticker={ticker}
currentTicker={currentTicker}
explorerLinkBase={'https://testnet.smartbit.com.au/'}
/>
)
}
return ( return (
<Channel <OpenPendingChannel
key={index} key={index}
channel={channel}
ticker={ticker} ticker={ticker}
currentTicker={currentTicker}
explorerLinkBase={'https://testnet.smartbit.com.au/'}
/>
)
} else if (Object.prototype.hasOwnProperty.call(channel, 'closing_txid')) {
return (
<ClosedPendingChannel
key={index}
channel={channel} channel={channel}
closeChannel={closeChannel} ticker={ticker}
currentTicker={currentTicker} currentTicker={currentTicker}
explorerLinkBase={'https://testnet.smartbit.com.au/'}
/> />
) )
}) }
} return (
</ul> <Channel
} key={index}
{ viewType === 1 && ticker={ticker}
<NetworkChannels channel={channel}
channels={openChannels} closeChannel={closeChannel}
network={network} currentTicker={currentTicker}
identity_pubkey={identity_pubkey} />
setCurrentChannel={setCurrentChannel} )
/> })
} }
</ul>
</div> </div>
</div> </div>
) )
@ -186,7 +155,6 @@ class Channels extends Component {
Channels.propTypes = { Channels.propTypes = {
fetchChannels: PropTypes.func.isRequired, fetchChannels: PropTypes.func.isRequired,
fetchPeers: PropTypes.func.isRequired,
channels: PropTypes.object.isRequired, channels: PropTypes.object.isRequired,
currentChannels: PropTypes.array.isRequired, currentChannels: PropTypes.array.isRequired,
@ -204,11 +172,7 @@ Channels.propTypes = {
ticker: PropTypes.object.isRequired, ticker: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired, currentTicker: PropTypes.object.isRequired,
channelFormProps: PropTypes.object.isRequired, channelFormProps: PropTypes.object.isRequired
network: PropTypes.object.isRequired,
fetchDescribeNetwork: PropTypes.func.isRequired,
identity_pubkey: PropTypes.string.isRequired
} }
export default Channels export default Channels

2
app/routes/channels/components/Channels.scss

@ -38,7 +38,7 @@
.header { .header {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: flex-end;
} }
.filtersContainer { .filtersContainer {

4
app/routes/network/components/Network.js

@ -115,9 +115,7 @@ class Network extends Component {
</ul> </ul>
<div className={styles.currentTab}> <div className={styles.currentTab}>
{ {renderTab()}
renderTab()
}
</div> </div>
</section> </section>
</div> </div>

68
app/routes/peers/components/Peers.js

@ -13,6 +13,14 @@ import Peer from 'components/Peers/Peer'
import styles from './Peers.scss' import styles from './Peers.scss'
class Peers extends Component { class Peers extends Component {
constructor(props) {
super(props)
this.state = {
refreshing: false
}
}
componentWillMount() { componentWillMount() {
this.props.fetchPeers() this.props.fetchPeers()
} }
@ -28,22 +36,34 @@ class Peers extends Component {
peerModalOpen, peerModalOpen,
filteredPeers, filteredPeers,
peers: { peer, searchQuery }, peers: { peer, searchQuery }
info: { data: { identity_pubkey } }
} = this.props } = this.props
const refreshClicked = event => { const refreshClicked = event => {
// turn the spinner on
this.setState({ refreshing: true })
// store event in icon so we dont get an error when react clears it // store event in icon so we dont get an error when react clears it
const icon = event.currentTarget let icon = this.refs.repeat.childNodes
// fetch peers // fetch peers
fetchPeers() fetchPeers()
// clear animation after the second so we can reuse it // wait for the svg to appear as child
setTimeout(() => { icon.style.animation = '' }, 1000) let svgTimeout = setTimeout(() => {
if (icon[0].tagName === 'svg') {
// spin icon for 1 sec
icon[0].style.animation = 'spin 1000ms linear 1'
clearTimeout(svgTimeout)
}
}, 1)
// spin icon for 1 sec // clear animation after the second so we can reuse it
icon.style.animation = 'spin 1000ms linear 1' let refreshTimeout = setTimeout(() => {
icon[0].style.animation = ''
this.setState({ refreshing: false })
clearTimeout(refreshTimeout)
}, 1000)
} }
return ( return (
@ -55,15 +75,7 @@ class Peers extends Component {
<header className={styles.header}> <header className={styles.header}>
<div className={styles.titleContainer}> <div className={styles.titleContainer}>
<div className={styles.left}> <div className={styles.left}>
<div className={styles.identityPubkey}> <h1>Peers</h1>
<section className={styles.userIcon}>
<Isvg src={userIcon} />
</section>
<section>
<h4>Your node public key</h4>
<h2>{identity_pubkey}</h2>
</section>
</div>
</div> </div>
</div> </div>
<div className={styles.addPeerContainer}> <div className={styles.addPeerContainer}>
@ -74,7 +86,6 @@ class Peers extends Component {
</header> </header>
<div className={styles.search}> <div className={styles.search}>
<label className={`${styles.label} ${styles.input}`} htmlFor='channelSearch'> <label className={`${styles.label} ${styles.input}`} htmlFor='channelSearch'>
<MdSearch /> <MdSearch />
</label> </label>
@ -89,10 +100,13 @@ class Peers extends Component {
</div> </div>
<div className={styles.refreshContainer}> <div className={styles.refreshContainer}>
<span className={`${styles.refresh} hint--top`} data-hint='Refresh your peers list'> <span className={styles.refresh} onClick={refreshClicked} ref='repeat'>
<FaRepeat {
onClick={refreshClicked} this.state.refreshing ?
/> <FaRepeat />
:
'Refresh'
}
</span> </span>
</div> </div>
@ -107,7 +121,17 @@ class Peers extends Component {
} }
Peers.propTypes = { Peers.propTypes = {
fetchPeers: PropTypes.func.isRequired,
peerFormProps: PropTypes.object.isRequired,
setPeerForm: PropTypes.func.isRequired,
setPeer: PropTypes.func.isRequired,
updateSearchQuery: PropTypes.func.isRequired,
disconnectRequest: PropTypes.func.isRequired,
peerModalOpen: PropTypes.bool.isRequired,
filteredPeers: PropTypes.array.isRequired,
peer: PropTypes.object,
searchQuery: PropTypes.string
} }
export default Peers export default Peers

55
app/routes/peers/components/Peers.scss

@ -1,7 +1,7 @@
@import '../../../variables.scss'; @import '../../../variables.scss';
.search { .search {
height: 75px; height: 55px;
padding: 2px 25px; padding: 2px 25px;
border-top: 1px solid $darkgrey; border-top: 1px solid $darkgrey;
border-bottom: 1px solid $darkgrey; border-bottom: 1px solid $darkgrey;
@ -15,8 +15,8 @@
.label { .label {
width: 5%; width: 5%;
line-height: 70px; line-height: 50px;
font-size: 25px; font-size: 20px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
} }
@ -27,8 +27,8 @@
padding: 0; padding: 0;
border: 0; border: 0;
border-radius: 0; border-radius: 0;
height: 68px; height: 50px;
font-size: 18px; font-size: 16px;
} }
} }
@ -36,58 +36,43 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
background: $lightgrey;
.titleContainer { .titleContainer {
padding: 40px; padding: 20px 40px;
.left { .left {
padding: 10px 0; padding: 10px 0;
}
.left, span {
display: inline-block;
}
.identityPubkey {
font-size: 30px;
margin-right: 10px;
display: flex;
flex-direction: row;
.userIcon { h1 {
margin-right: 10px;
}
section h4 {
font-size: 10px;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 1.2px; font-size: 26px;
} margin-right: 5px;
section h2 {
font-size: 14px;
margin-top: 5px;
} }
} }
} }
.addPeerContainer { .addPeerContainer {
padding: 40px; padding: 20px 40px;
.newPeerButton { .newPeerButton {
font-size: 14px; font-size: 14px;
margin-left: 10px;
} }
} }
} }
.refreshContainer { .refreshContainer {
display: flex; padding: 20px 40px 0 40px;
flex-direction: row; text-align: right;
padding: 20px 40px 0px 40px; cursor: pointer;
.refresh { .refresh {
cursor: pointer; text-decoration: underline;
color: $bluegrey;
svg {
font-size: 12px;
}
} }
} }

Loading…
Cancel
Save