Jack Mallers
7 years ago
28 changed files with 0 additions and 2162 deletions
@ -1,38 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import { FaCircle } from 'react-icons/lib/fa' |
|
||||
import { btc, blockExplorer } from 'utils' |
|
||||
import styles from './Contact.scss' |
|
||||
|
|
||||
const ClosingContact = ({ channel }) => ( |
|
||||
<li className={styles.friend}> |
|
||||
<section className={styles.info}> |
|
||||
<p className={styles.closing}> |
|
||||
<FaCircle style={{ verticalAlign: 'top' }} /> |
|
||||
<span> |
|
||||
Removing |
|
||||
<i onClick={() => blockExplorer.showChannelClosing(channel)}> |
|
||||
(Details) |
|
||||
</i> |
|
||||
</span> |
|
||||
</p> |
|
||||
<h2>{channel.channel.remote_node_pub}</h2> |
|
||||
</section> |
|
||||
<section className={styles.limits}> |
|
||||
<div> |
|
||||
<h4>Can Pay</h4> |
|
||||
<p>{btc.satoshisToBtc(channel.channel.local_balance)}BTC</p> |
|
||||
</div> |
|
||||
<div> |
|
||||
<h4>Can Receive</h4> |
|
||||
<p>{btc.satoshisToBtc(channel.channel.remote_balance)}BTC</p> |
|
||||
</div> |
|
||||
</section> |
|
||||
</li> |
|
||||
) |
|
||||
|
|
||||
ClosingContact.propTypes = { |
|
||||
channel: PropTypes.object.isRequired |
|
||||
} |
|
||||
|
|
||||
export default ClosingContact |
|
@ -1,156 +0,0 @@ |
|||||
@import '../../variables.scss'; |
|
||||
|
|
||||
.friend { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: space-between; |
|
||||
padding: 30px 60px 60px 60px; |
|
||||
cursor: pointer; |
|
||||
transition: all 0.25s; |
|
||||
|
|
||||
&.loading { |
|
||||
.info { |
|
||||
opacity: 0.2; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
&:hover { |
|
||||
background: $lightgrey; |
|
||||
} |
|
||||
|
|
||||
.limits { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: space-between; |
|
||||
|
|
||||
div { |
|
||||
margin: 0 10px; |
|
||||
|
|
||||
h4 { |
|
||||
font-size: 12px; |
|
||||
margin-bottom: 20px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.info { |
|
||||
p { |
|
||||
margin-bottom: 20px; |
|
||||
|
|
||||
&.online { |
|
||||
color: $green; |
|
||||
|
|
||||
svg { |
|
||||
color: $green; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
&.pending { |
|
||||
color: $orange; |
|
||||
|
|
||||
svg { |
|
||||
color: $orange; |
|
||||
} |
|
||||
|
|
||||
i { |
|
||||
margin-left: 5px; |
|
||||
color: $darkestgrey; |
|
||||
cursor: pointer; |
|
||||
|
|
||||
&:hover { |
|
||||
text-decoration: underline; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
&.closing { |
|
||||
color: $red; |
|
||||
|
|
||||
svg { |
|
||||
color: $red; |
|
||||
} |
|
||||
|
|
||||
i { |
|
||||
margin-left: 5px; |
|
||||
color: $darkestgrey; |
|
||||
cursor: pointer; |
|
||||
|
|
||||
&:hover { |
|
||||
text-decoration: underline; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
svg, span { |
|
||||
display: inline-block; |
|
||||
vertical-align: top; |
|
||||
} |
|
||||
|
|
||||
svg { |
|
||||
margin-right: 5px; |
|
||||
width: 12px; |
|
||||
height: 12px; |
|
||||
color: $darkestgrey; |
|
||||
} |
|
||||
|
|
||||
span { |
|
||||
font-size: 12px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
h2 { |
|
||||
color: $black; |
|
||||
font-size: 14px; |
|
||||
font-weight: bold; |
|
||||
letter-spacing: 1.3px; |
|
||||
|
|
||||
span { |
|
||||
color: $darkestgrey; |
|
||||
margin-left: 5px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@-webkit-keyframes animation-rotate { |
|
||||
100% { |
|
||||
-webkit-transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@-moz-keyframes animation-rotate { |
|
||||
100% { |
|
||||
-moz-transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@-o-keyframes animation-rotate { |
|
||||
100% { |
|
||||
-o-transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@keyframes animation-rotate { |
|
||||
100% { |
|
||||
transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.spinner { |
|
||||
border: 1px solid rgba(0, 0, 0, 0.1); |
|
||||
border-left-color: rgba(0, 0, 0, 0.4); |
|
||||
-webkit-border-radius: 999px; |
|
||||
-moz-border-radius: 999px; |
|
||||
border-radius: 999px; |
|
||||
} |
|
||||
|
|
||||
.spinner { |
|
||||
margin: 0 auto; |
|
||||
height: 50px; |
|
||||
width: 50px; |
|
||||
-webkit-animation: animation-rotate 1000ms linear infinite; |
|
||||
-moz-animation: animation-rotate 1000ms linear infinite; |
|
||||
-o-animation: animation-rotate 1000ms linear infinite; |
|
||||
animation: animation-rotate 1000ms linear infinite; |
|
||||
} |
|
||||
|
|
@ -1,35 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import { FaCircle } from 'react-icons/lib/fa' |
|
||||
import styles from './Contact.scss' |
|
||||
|
|
||||
const LoadingContact = ({ pubkey, isClosing }) => ( |
|
||||
<li className={`${styles.friend} ${styles.loading}`}> |
|
||||
<section className={styles.info}> |
|
||||
<p> |
|
||||
<FaCircle style={{ verticalAlign: 'top' }} /> |
|
||||
<span> |
|
||||
{ |
|
||||
isClosing ? |
|
||||
'Closing' |
|
||||
: |
|
||||
'Loading' |
|
||||
} |
|
||||
</span> |
|
||||
</p> |
|
||||
<h2>{pubkey}</h2> |
|
||||
</section> |
|
||||
<section className={styles.limits}> |
|
||||
<div className={styles.loading}> |
|
||||
<div className={styles.spinner} /> |
|
||||
</div> |
|
||||
</section> |
|
||||
</li> |
|
||||
) |
|
||||
|
|
||||
LoadingContact.propTypes = { |
|
||||
pubkey: PropTypes.string.isRequired, |
|
||||
isClosing: PropTypes.bool.isRequired |
|
||||
} |
|
||||
|
|
||||
export default LoadingContact |
|
@ -1,34 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import { FaCircle } from 'react-icons/lib/fa' |
|
||||
import { btc } from 'utils' |
|
||||
import styles from './Contact.scss' |
|
||||
|
|
||||
const OfflineContact = ({ channel, openContactModal }) => ( |
|
||||
<li className={styles.friend} key={channel.chan_id} onClick={() => openContactModal(channel)}> |
|
||||
<section className={styles.info}> |
|
||||
<p> |
|
||||
<FaCircle style={{ verticalAlign: 'top' }} /> |
|
||||
<span>Offline</span> |
|
||||
</p> |
|
||||
<h2>{channel.remote_pubkey}</h2> |
|
||||
</section> |
|
||||
<section className={styles.limits}> |
|
||||
<div> |
|
||||
<h4>Can Pay</h4> |
|
||||
<p>{btc.satoshisToBtc(channel.local_balance)}BTC</p> |
|
||||
</div> |
|
||||
<div> |
|
||||
<h4>Can Receive</h4> |
|
||||
<p>{btc.satoshisToBtc(channel.remote_balance)}BTC</p> |
|
||||
</div> |
|
||||
</section> |
|
||||
</li> |
|
||||
) |
|
||||
|
|
||||
OfflineContact.propTypes = { |
|
||||
channel: PropTypes.object.isRequired, |
|
||||
openContactModal: PropTypes.func.isRequired |
|
||||
} |
|
||||
|
|
||||
export default OfflineContact |
|
@ -1,34 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import { FaCircle } from 'react-icons/lib/fa' |
|
||||
import { btc } from 'utils' |
|
||||
import styles from './Contact.scss' |
|
||||
|
|
||||
const OnlineContact = ({ channel, openContactModal }) => ( |
|
||||
<li className={styles.friend} key={channel.chan_id} onClick={() => openContactModal(channel)}> |
|
||||
<section className={styles.info}> |
|
||||
<p className={styles.online}> |
|
||||
<FaCircle style={{ verticalAlign: 'top' }} /> |
|
||||
<span>Online</span> |
|
||||
</p> |
|
||||
<h2>{channel.remote_pubkey}</h2> |
|
||||
</section> |
|
||||
<section className={styles.limits}> |
|
||||
<div> |
|
||||
<h4>Can Pay</h4> |
|
||||
<p>{btc.satoshisToBtc(channel.local_balance)}BTC</p> |
|
||||
</div> |
|
||||
<div> |
|
||||
<h4>Can Receive</h4> |
|
||||
<p>{btc.satoshisToBtc(channel.remote_balance)}BTC</p> |
|
||||
</div> |
|
||||
</section> |
|
||||
</li> |
|
||||
) |
|
||||
|
|
||||
OnlineContact.propTypes = { |
|
||||
channel: PropTypes.object.isRequired, |
|
||||
openContactModal: PropTypes.func.isRequired |
|
||||
} |
|
||||
|
|
||||
export default OnlineContact |
|
@ -1,38 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import { FaCircle } from 'react-icons/lib/fa' |
|
||||
import { btc, blockExplorer } from 'utils' |
|
||||
import styles from './Contact.scss' |
|
||||
|
|
||||
const PendingContact = ({ channel }) => ( |
|
||||
<li className={styles.friend} key={channel.chan_id}> |
|
||||
<section className={styles.info}> |
|
||||
<p className={styles.pending}> |
|
||||
<FaCircle style={{ verticalAlign: 'top' }} /> |
|
||||
<span> |
|
||||
Pending |
|
||||
<i onClick={() => blockExplorer.showChannelPoint(channel)}> |
|
||||
(Details) |
|
||||
</i> |
|
||||
</span> |
|
||||
</p> |
|
||||
<h2>{channel.channel.remote_node_pub}</h2> |
|
||||
</section> |
|
||||
<section className={styles.limits}> |
|
||||
<div> |
|
||||
<h4>Can Pay</h4> |
|
||||
<p>{btc.satoshisToBtc(channel.channel.local_balance)}BTC</p> |
|
||||
</div> |
|
||||
<div> |
|
||||
<h4>Can Receive</h4> |
|
||||
<p>{btc.satoshisToBtc(channel.channel.remote_balance)}BTC</p> |
|
||||
</div> |
|
||||
</section> |
|
||||
</li> |
|
||||
) |
|
||||
|
|
||||
PendingContact.propTypes = { |
|
||||
channel: PropTypes.object.isRequired |
|
||||
} |
|
||||
|
|
||||
export default PendingContact |
|
@ -1,274 +0,0 @@ |
|||||
import React, { Component } from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import * as d3Force from 'd3-force' |
|
||||
import * as d3Selection from 'd3-selection' |
|
||||
import * as d3Zoom from 'd3-zoom' |
|
||||
|
|
||||
import styles from './CanvasNetworkGraph.scss' |
|
||||
|
|
||||
const d3 = Object.assign({}, d3Force, d3Selection, d3Zoom) |
|
||||
|
|
||||
function generateSimulationData(nodes, edges) { |
|
||||
const resNodes = nodes.map(node => Object.assign(node, { id: node.pub_key })) |
|
||||
const resEdges = edges.map(node => Object.assign(node, { source: node.node1_pub, target: node.node2_pub })) |
|
||||
|
|
||||
return { |
|
||||
nodes: resNodes, |
|
||||
links: resEdges |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
class CanvasNetworkGraph extends Component { |
|
||||
constructor(props) { |
|
||||
super(props) |
|
||||
|
|
||||
this.state = { |
|
||||
simulationData: { |
|
||||
nodes: [], |
|
||||
links: [] |
|
||||
}, |
|
||||
|
|
||||
svgLoaded: false |
|
||||
} |
|
||||
|
|
||||
this.startSimulation = this.startSimulation.bind(this) |
|
||||
this.zoomActions = this.zoomActions.bind(this) |
|
||||
this.ticked = this.ticked.bind(this) |
|
||||
this.restart = this.restart.bind(this) |
|
||||
} |
|
||||
|
|
||||
componentDidMount() { |
|
||||
// wait for the svg to be in the DOM before we start the simulation
|
|
||||
const svgInterval = setInterval(() => { |
|
||||
if (document.getElementById('mapContainer')) { |
|
||||
d3.select('#mapContainer') |
|
||||
.append('svg') |
|
||||
.attr('id', 'map') |
|
||||
.attr('width', '100%') |
|
||||
.attr('height', '100%') |
|
||||
|
|
||||
this.startSimulation() |
|
||||
|
|
||||
clearInterval(svgInterval) |
|
||||
} |
|
||||
}, 1000) |
|
||||
} |
|
||||
|
|
||||
componentWillReceiveProps(nextProps) { |
|
||||
const { network } = nextProps |
|
||||
const { simulationData: { nodes, links } } = this.state |
|
||||
|
|
||||
const simulationDataEmpty = !nodes.length && !links.length |
|
||||
const networkDataLoaded = network.nodes.length || network.edges.length |
|
||||
const prevNetwork = this.props.network |
|
||||
|
|
||||
if ( |
|
||||
// update the simulationData only if
|
|
||||
// the simulationData is empty and we have network data
|
|
||||
(simulationDataEmpty && networkDataLoaded) || |
|
||||
// the nodes or edges have changed
|
|
||||
(prevNetwork.nodes.length !== network.nodes.length || prevNetwork.edges.length !== network.edges.length)) { |
|
||||
this.setState({ |
|
||||
simulationData: generateSimulationData(network.nodes, network.edges) |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
componentDidUpdate(prevProps) { |
|
||||
const { |
|
||||
selectedPeerPubkeys, |
|
||||
selectedChannelIds, |
|
||||
currentRouteChanIds |
|
||||
} = this.props |
|
||||
|
|
||||
if (prevProps.selectedPeerPubkeys.length !== selectedPeerPubkeys.length) { |
|
||||
this.updateSelectedPeers() |
|
||||
} |
|
||||
|
|
||||
if (prevProps.selectedChannelIds.length !== selectedChannelIds.length) { |
|
||||
this.updateSelectedChannels() |
|
||||
} |
|
||||
|
|
||||
if (prevProps.currentRouteChanIds.length !== currentRouteChanIds.length) { |
|
||||
this.renderSelectedRoute() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
componentWillUnmount() { |
|
||||
d3.select('#map') |
|
||||
.remove() |
|
||||
} |
|
||||
|
|
||||
updateSelectedPeers() { |
|
||||
const { selectedPeerPubkeys } = this.props |
|
||||
|
|
||||
// remove active class
|
|
||||
d3.selectAll('.active-peer').classed('active-peer', false) |
|
||||
|
|
||||
// add active class to all selected peers
|
|
||||
selectedPeerPubkeys.forEach((pubkey) => { |
|
||||
d3.select(`#node-${pubkey}`).classed('active-peer', true) |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
updateSelectedChannels() { |
|
||||
const { selectedChannelIds } = this.props |
|
||||
|
|
||||
// remove active class
|
|
||||
d3.selectAll('.active-channel').classed('active-channel', false) |
|
||||
|
|
||||
// add active class to all selected peers
|
|
||||
selectedChannelIds.forEach((chanid) => { |
|
||||
d3.select(`#link-${chanid}`).classed('active-channel', true) |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
startSimulation() { |
|
||||
const { simulationData: { nodes, links } } = this.state |
|
||||
|
|
||||
// grab the svg el along with the attributes
|
|
||||
const svg = d3.select('#map') |
|
||||
const svgBox = svg.node().getBBox() |
|
||||
|
|
||||
this.g = svg.append('g').attr('transform', `translate(${svgBox.width / 2},${svgBox.height / 2})`) |
|
||||
this.link = this.g.append('g').attr('stroke', 'white').attr('stroke-width', 1.5).selectAll('.link') |
|
||||
this.node = this.g.append('g').attr('stroke', 'silver').attr('stroke-width', 1.5).selectAll('.node') |
|
||||
|
|
||||
this.simulation = d3.forceSimulation(nodes) |
|
||||
.force('charge', d3.forceManyBody().strength(-750)) |
|
||||
.force('link', d3.forceLink(links).id(d => d.pub_key).distance(500)) |
|
||||
.force('collide', d3.forceCollide(300)) |
|
||||
.on('tick', this.ticked) |
|
||||
.on('end', () => { |
|
||||
this.setState({ svgLoaded: true }) |
|
||||
}) |
|
||||
// zoom
|
|
||||
const zoom_handler = d3.zoom().on('zoom', this.zoomActions) |
|
||||
zoom_handler(svg) |
|
||||
|
|
||||
this.restart() |
|
||||
} |
|
||||
|
|
||||
zoomActions() { |
|
||||
this.g.attr('transform', d3Selection.event.transform) |
|
||||
} |
|
||||
|
|
||||
ticked() { |
|
||||
this.node.attr('cx', d => d.x) |
|
||||
.attr('cy', d => d.y) |
|
||||
|
|
||||
this.link.attr('x1', d => d.source.x) |
|
||||
.attr('y1', d => d.source.y) |
|
||||
.attr('x2', d => d.target.x) |
|
||||
.attr('y2', d => d.target.y) |
|
||||
} |
|
||||
|
|
||||
restart() { |
|
||||
const { identity_pubkey } = this.props |
|
||||
const { simulationData: { nodes, links } } = this.state |
|
||||
|
|
||||
// Apply the general update pattern to the nodes.
|
|
||||
this.node = this.node.data(nodes, d => d.pub_key) |
|
||||
this.node.exit().remove() |
|
||||
this.node = this.node.enter() |
|
||||
.append('circle') |
|
||||
.attr('stroke', () => 'silver') |
|
||||
.attr('fill', d => (d.pub_key === identity_pubkey ? '#FFF' : '#353535')) |
|
||||
.attr('r', () => 100) |
|
||||
.attr('id', d => `node-${d.pub_key}`) |
|
||||
.attr('class', 'network-node') |
|
||||
.merge(this.node) |
|
||||
|
|
||||
// Apply the general update pattern to the links.
|
|
||||
this.link = this.link.data(links, d => `${d.source.id}-${d.target.id}`) |
|
||||
this.link.exit().remove() |
|
||||
this.link = |
|
||||
this.link.enter() |
|
||||
.append('line') |
|
||||
.attr('id', d => `link-${d.channel_id}`) |
|
||||
.attr('class', 'network-link') |
|
||||
.merge(this.link) |
|
||||
|
|
||||
// Update and restart the simulation.
|
|
||||
this.simulation.nodes(nodes) |
|
||||
this.simulation.force('link').links(links) |
|
||||
this.simulation.restart() |
|
||||
} |
|
||||
|
|
||||
renderSelectedRoute() { |
|
||||
const { currentRouteChanIds } = this.props |
|
||||
|
|
||||
// remove all route animations before rendering new ones
|
|
||||
d3.selectAll('.animated-route-circle').remove() |
|
||||
|
|
||||
currentRouteChanIds.forEach((chanId) => { |
|
||||
const link = document.getElementById(`link-${chanId}`) |
|
||||
|
|
||||
if (!link) { return } |
|
||||
const x1 = link.x1.baseVal.value |
|
||||
const x2 = link.x2.baseVal.value |
|
||||
const y1 = link.y1.baseVal.value |
|
||||
const y2 = link.y2.baseVal.value |
|
||||
|
|
||||
// create the circle that represent btc traveling through a channel
|
|
||||
this.g |
|
||||
.append('circle') |
|
||||
.attr('id', `circle-${chanId}`) |
|
||||
.attr('class', 'animated-route-circle') |
|
||||
.attr('r', 50) |
|
||||
.attr('cx', x1) |
|
||||
.attr('cy', y1) |
|
||||
.attr('fill', '#FFDC53') |
|
||||
|
|
||||
// we want the animation to repeat back and forth, this function executes that visually
|
|
||||
const repeat = () => { |
|
||||
d3.select(`#circle-${chanId}`) |
|
||||
.transition() |
|
||||
.attr('cx', x2) |
|
||||
.attr('cy', y2) |
|
||||
.duration(1000) |
|
||||
.transition() |
|
||||
.duration(1000) |
|
||||
.attr('cx', x1) |
|
||||
.attr('cy', y1) |
|
||||
.on('end', repeat) |
|
||||
} |
|
||||
|
|
||||
// call repeat to animate the circle
|
|
||||
repeat() |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
render() { |
|
||||
const { svgLoaded } = this.state |
|
||||
|
|
||||
return ( |
|
||||
<div className={styles.mapContainer} id='mapContainer'> |
|
||||
{ |
|
||||
!svgLoaded && |
|
||||
<div className={styles.loadingContainer}> |
|
||||
<div className={styles.loadingWrap}> |
|
||||
<div className={styles.loader} /> |
|
||||
<div className={styles.loaderbefore} /> |
|
||||
<div className={styles.circular} /> |
|
||||
<div className={`${styles.circular} ${styles.another}`} /> |
|
||||
<div className={styles.text}>loading</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
} |
|
||||
</div> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
CanvasNetworkGraph.propTypes = { |
|
||||
identity_pubkey: PropTypes.string.isRequired, |
|
||||
|
|
||||
network: PropTypes.object.isRequired, |
|
||||
|
|
||||
selectedPeerPubkeys: PropTypes.array.isRequired, |
|
||||
selectedChannelIds: PropTypes.array.isRequired, |
|
||||
currentRouteChanIds: PropTypes.array.isRequired |
|
||||
} |
|
||||
|
|
||||
export default CanvasNetworkGraph |
|
@ -1,156 +0,0 @@ |
|||||
@import '../../variables.scss'; |
|
||||
|
|
||||
@keyframes fadein { |
|
||||
0% { background: $white; } |
|
||||
50% { background: lighten($secondary, 50%); } |
|
||||
100% { background: $secondary; animation-fill-mode:forwards; } |
|
||||
} |
|
||||
|
|
||||
.mapContainer { |
|
||||
position: relative; |
|
||||
display: inline-block; |
|
||||
width: 70%; |
|
||||
height: 100%; |
|
||||
} |
|
||||
|
|
||||
.loadingContainer { |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
left: 0; |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
background: $secondary; |
|
||||
} |
|
||||
|
|
||||
.loadingWrap { |
|
||||
position: relative; |
|
||||
top: calc(50% - 150px); |
|
||||
width: 150px; |
|
||||
margin: 0 auto; |
|
||||
} |
|
||||
|
|
||||
.loader { |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
z-index: 10; |
|
||||
width: 50px; |
|
||||
height: 50px; |
|
||||
border: 15px solid; |
|
||||
border-radius: 50%; |
|
||||
border-top-color: rgba(44,44,44,0); |
|
||||
border-right-color: rgba(55,55,55,0); |
|
||||
border-bottom-color: rgba(66,66,66,0); |
|
||||
border-left-color: rgba(33,33,33,0); |
|
||||
animation: loadEr 3s infinite; |
|
||||
} |
|
||||
|
|
||||
@keyframes loadEr { |
|
||||
0% { |
|
||||
border-top-color: rgba(44,44,44,0); |
|
||||
border-right-color: rgba(55,55,55,0); |
|
||||
border-bottom-color: rgba(66,66,66,0); |
|
||||
border-left-color: rgba(33,33,33,0); |
|
||||
|
|
||||
} |
|
||||
10.4% { |
|
||||
border-top-color: rgba(44,44,44,0.5); |
|
||||
border-right-color: rgba(55,55,55,0); |
|
||||
border-bottom-color: rgba(66,66,66,0); |
|
||||
border-left-color: rgba(33,33,33,0); |
|
||||
} |
|
||||
20.8% { |
|
||||
border-top-color: rgba(44,44,44,0); |
|
||||
border-right-color: rgba(55,55,55,0); |
|
||||
border-bottom-color: rgba(66,66,66,0); |
|
||||
border-left-color: rgba(33,33,33,0); |
|
||||
} |
|
||||
31.2% { |
|
||||
border-top-color: rgba(44,44,44,0); |
|
||||
border-right-color: rgba(55,55,55,0.5); |
|
||||
border-bottom-color: rgba(66,66,66,0); |
|
||||
border-left-color: rgba(33,33,33,0); |
|
||||
} |
|
||||
41.6% { |
|
||||
border-top-color: rgba(44,44,44,0); |
|
||||
border-right-color: rgba(55,55,55,0); |
|
||||
border-bottom-color: rgba(66,66,66,0); |
|
||||
border-left-color: rgba(33,33,33,0); |
|
||||
transform: rotate(40deg); |
|
||||
} |
|
||||
52% { |
|
||||
border-top-color: rgba(44,44,44,0); |
|
||||
border-right-color: rgba(55,55,55,0); |
|
||||
border-bottom-color: rgba(66,66,66,0.5); |
|
||||
border-left-color: rgba(33,33,33,0); |
|
||||
} |
|
||||
62.4% { |
|
||||
border-top-color: rgba(44,44,44,0); |
|
||||
border-right-color: rgba(55,55,55,0); |
|
||||
border-bottom-color: rgba(66,66,66,0); |
|
||||
border-left-color: rgba(33,33,33,0); |
|
||||
} |
|
||||
72.8% { |
|
||||
border-top-color: rgba(44,44,44,0); |
|
||||
border-right-color: rgba(55,55,55,0); |
|
||||
border-bottom-color: rgba(66,66,66,0); |
|
||||
border-left-color: rgba(33,33,33,0.5); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.loaderbefore { |
|
||||
width: 50px; |
|
||||
height:50px; |
|
||||
border: 15px solid #ddd; |
|
||||
border-radius: 50%; |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
z-index: 9; |
|
||||
} |
|
||||
|
|
||||
.circular { |
|
||||
position: absolute; |
|
||||
top: -15px; |
|
||||
left: -15px; |
|
||||
width: 70px; |
|
||||
height: 70px; |
|
||||
border: 20px solid; |
|
||||
border-radius: 50%; |
|
||||
border-top-color: #333; |
|
||||
border-left-color: #fff; |
|
||||
border-bottom-color: #333; |
|
||||
border-right-color: #fff; |
|
||||
opacity: 0.2; |
|
||||
animation: poof 5s infinite; |
|
||||
} |
|
||||
@keyframes poof { |
|
||||
0% {transform: scale(1,1) rotate(0deg); opacity: 0.2;} |
|
||||
50% {transform: scale(4,4) rotate(360deg); opacity: 0;} |
|
||||
} |
|
||||
.another { |
|
||||
opacity: 0.1; |
|
||||
transform: rotate(90deg); |
|
||||
animation: poofity 5s infinite; |
|
||||
animation-delay: 1s; |
|
||||
} |
|
||||
@keyframes poofity { |
|
||||
0% {transform: scale(1,1) rotate(90deg); opacity: 0.1;} |
|
||||
50% {transform: scale(4,4) rotate(-360deg); opacity: 0;} |
|
||||
} |
|
||||
|
|
||||
.text { |
|
||||
position: absolute; |
|
||||
top: 95px; |
|
||||
left: 8px; |
|
||||
font-family: Arial; |
|
||||
text-transform: uppercase; |
|
||||
color: #888; |
|
||||
animation: opaa 10s infinite; |
|
||||
} |
|
||||
@keyframes opaa { |
|
||||
0% {opacity: 1;} |
|
||||
10% {opacity: 0.5} |
|
||||
15% {opacity: 1;} |
|
||||
30% {opacity: 1;} |
|
||||
65% {opacity: 0.3;} |
|
||||
90% {opacity: 0.8;} |
|
||||
} |
|
@ -1,45 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import { btc, blockExplorer } from 'utils' |
|
||||
import styles from './ChannelsList.scss' |
|
||||
|
|
||||
const ChannelsList = ({ channels, updateSelectedChannels, selectedChannelIds }) => ( |
|
||||
<ul className={styles.channels}> |
|
||||
{ |
|
||||
channels.map(channel => ( |
|
||||
<li key={channel.chan_id} className={styles.channel} onClick={() => updateSelectedChannels(channel)}> |
|
||||
<span className={`${styles.dot} ${selectedChannelIds.includes(channel.chan_id) && styles.active}`} /> |
|
||||
|
|
||||
<header> |
|
||||
<h1>Capacity: {btc.satoshisToBtc(channel.capacity)}</h1> |
|
||||
<span onClick={() => blockExplorer.showChannelPoint({ channel })}>Channel Point</span> |
|
||||
</header> |
|
||||
|
|
||||
<section> |
|
||||
<h4>Remote Pubkey:</h4> |
|
||||
<p>{channel.remote_pubkey.substring(0, Math.min(30, channel.remote_pubkey.length))}...</p> |
|
||||
</section> |
|
||||
|
|
||||
<section className={styles.funds}> |
|
||||
<div> |
|
||||
<h4>Sent:</h4> |
|
||||
<p>{btc.satoshisToBtc(channel.total_satoshis_sent)} BTC</p> |
|
||||
</div> |
|
||||
<div> |
|
||||
<h4>Received:</h4> |
|
||||
<p>{btc.satoshisToBtc(channel.total_satoshis_received)} BTC</p> |
|
||||
</div> |
|
||||
</section> |
|
||||
</li> |
|
||||
)) |
|
||||
} |
|
||||
</ul> |
|
||||
) |
|
||||
|
|
||||
ChannelsList.propTypes = { |
|
||||
channels: PropTypes.array.isRequired, |
|
||||
updateSelectedChannels: PropTypes.func.isRequired, |
|
||||
selectedChannelIds: PropTypes.array.isRequired |
|
||||
} |
|
||||
|
|
||||
export default ChannelsList |
|
@ -1,78 +0,0 @@ |
|||||
@import '../../variables.scss'; |
|
||||
|
|
||||
.channels { |
|
||||
color: $white; |
|
||||
margin-top: 50px; |
|
||||
} |
|
||||
|
|
||||
.channel { |
|
||||
position: relative; |
|
||||
margin: 20px 0; |
|
||||
padding: 10px 40px; |
|
||||
cursor: pointer; |
|
||||
transition: all 0.25s; |
|
||||
|
|
||||
&:hover { |
|
||||
background: darken(#353535, 10%); |
|
||||
|
|
||||
.dot { |
|
||||
background: #88D4A2; |
|
||||
opacity: 0.5; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.dot { |
|
||||
position: absolute; |
|
||||
top: calc(15% - 10px); |
|
||||
left: 5%; |
|
||||
width: 10px; |
|
||||
height: 10px; |
|
||||
border: 1px solid #979797; |
|
||||
border-radius: 50%; |
|
||||
|
|
||||
&.active { |
|
||||
background: #88D4A2; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
header { |
|
||||
margin-bottom: 10px; |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: space-between; |
|
||||
|
|
||||
h1 { |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
|
|
||||
span { |
|
||||
font-size: 10px; |
|
||||
text-decoration: underline; |
|
||||
transition: all 0.25s; |
|
||||
|
|
||||
&:hover { |
|
||||
color: #88D4A2; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
section { |
|
||||
margin: 10px 0; |
|
||||
|
|
||||
h4 { |
|
||||
font-weight: bold; |
|
||||
text-transform: uppercase; |
|
||||
font-size: 10px; |
|
||||
margin-bottom: 5px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.funds { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
margin-top: 20px; |
|
||||
} |
|
||||
} |
|
@ -1,25 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import styles from './PeersList.scss' |
|
||||
|
|
||||
const PeersList = ({ peers, updateSelectedPeers, selectedPeerPubkeys }) => ( |
|
||||
<ul className={styles.peers}> |
|
||||
{ |
|
||||
peers.map(peer => ( |
|
||||
<li key={peer.peer_id} className={styles.peer} onClick={() => updateSelectedPeers(peer)}> |
|
||||
<span className={`${styles.dot} ${selectedPeerPubkeys.includes(peer.pub_key) && styles.active}`} /> |
|
||||
<h1>{peer.address}</h1> |
|
||||
<h4>{peer.pub_key}</h4> |
|
||||
</li> |
|
||||
)) |
|
||||
} |
|
||||
</ul> |
|
||||
) |
|
||||
|
|
||||
PeersList.propTypes = { |
|
||||
peers: PropTypes.array.isRequired, |
|
||||
updateSelectedPeers: PropTypes.func.isRequired, |
|
||||
selectedPeerPubkeys: PropTypes.array.isRequired |
|
||||
} |
|
||||
|
|
||||
export default PeersList |
|
@ -1,46 +0,0 @@ |
|||||
@import '../../variables.scss'; |
|
||||
|
|
||||
.peers { |
|
||||
color: $white; |
|
||||
margin-top: 50px; |
|
||||
} |
|
||||
|
|
||||
.peer { |
|
||||
position: relative; |
|
||||
margin: 20px 0; |
|
||||
padding: 10px 40px; |
|
||||
cursor: pointer; |
|
||||
transition: all 0.25s; |
|
||||
|
|
||||
&:hover { |
|
||||
background: darken(#353535, 10%); |
|
||||
|
|
||||
.dot { |
|
||||
background: #5589F3; |
|
||||
opacity: 0.5; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.dot { |
|
||||
position: absolute; |
|
||||
top: calc(50% - 10px); |
|
||||
left: 5%; |
|
||||
width: 10px; |
|
||||
height: 10px; |
|
||||
border: 1px solid #979797; |
|
||||
border-radius: 50%; |
|
||||
|
|
||||
&.active { |
|
||||
background: #5589F3; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
h1 { |
|
||||
font-size: 16px; |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
|
|
||||
h4 { |
|
||||
font-size: 8px; |
|
||||
} |
|
||||
} |
|
@ -1,63 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import { btc } from 'utils' |
|
||||
import styles from './TransactionForm.scss' |
|
||||
|
|
||||
const TransactionForm = ({ |
|
||||
updatePayReq, pay_req, loadingRoutes, payReqRoutes, setCurrentRoute, currentRoute |
|
||||
}) => ( |
|
||||
<div className={styles.transactionForm}> |
|
||||
<div className={styles.form}> |
|
||||
<input |
|
||||
className={styles.transactionInput} |
|
||||
placeholder='Payment request...' |
|
||||
value={pay_req} |
|
||||
onChange={event => updatePayReq(event.target.value)} |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
{ |
|
||||
loadingRoutes && |
|
||||
<div className={styles.loading}> |
|
||||
<div className={styles.spinner} /> |
|
||||
<h1>calculating all routes...</h1> |
|
||||
</div> |
|
||||
} |
|
||||
|
|
||||
<ul className={styles.routes}> |
|
||||
{ |
|
||||
payReqRoutes.map((route, index) => ( |
|
||||
<li className={`${styles.route} ${currentRoute === route && styles.active}`} key={index} onClick={() => setCurrentRoute(route)}> |
|
||||
<header> |
|
||||
<h1>Route #{index + 1}</h1> |
|
||||
<span>Hops: {route.hops.length}</span> |
|
||||
</header> |
|
||||
|
|
||||
<div className={styles.data}> |
|
||||
<section> |
|
||||
<h4>Amount</h4> |
|
||||
<span>{btc.satoshisToBtc(route.total_amt)} BTC</span> |
|
||||
</section> |
|
||||
|
|
||||
<section> |
|
||||
<h4>Fees</h4> |
|
||||
<span>{btc.satoshisToBtc(route.total_fees)} BTC</span> |
|
||||
</section> |
|
||||
</div> |
|
||||
</li> |
|
||||
)) |
|
||||
} |
|
||||
</ul> |
|
||||
</div> |
|
||||
) |
|
||||
|
|
||||
TransactionForm.propTypes = { |
|
||||
updatePayReq: PropTypes.func.isRequired, |
|
||||
pay_req: PropTypes.string.isRequired, |
|
||||
loadingRoutes: PropTypes.bool.isRequired, |
|
||||
payReqRoutes: PropTypes.array.isRequired, |
|
||||
setCurrentRoute: PropTypes.func.isRequired, |
|
||||
currentRoute: PropTypes.object.isRequired |
|
||||
} |
|
||||
|
|
||||
export default TransactionForm |
|
@ -1,125 +0,0 @@ |
|||||
@import '../../variables.scss'; |
|
||||
|
|
||||
@-webkit-keyframes animation-rotate { |
|
||||
100% { |
|
||||
-webkit-transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@-moz-keyframes animation-rotate { |
|
||||
100% { |
|
||||
-moz-transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@-o-keyframes animation-rotate { |
|
||||
100% { |
|
||||
-o-transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@keyframes animation-rotate { |
|
||||
100% { |
|
||||
transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.spinner { |
|
||||
border: 1px solid rgba(255, 220, 83, 0.1); |
|
||||
border-left-color: rgba(255, 220, 83, 0.4); |
|
||||
-webkit-border-radius: 999px; |
|
||||
-moz-border-radius: 999px; |
|
||||
border-radius: 999px; |
|
||||
} |
|
||||
|
|
||||
.spinner { |
|
||||
margin: 0 auto; |
|
||||
height: 100px; |
|
||||
width: 100px; |
|
||||
-webkit-animation: animation-rotate 1000ms linear infinite; |
|
||||
-moz-animation: animation-rotate 1000ms linear infinite; |
|
||||
-o-animation: animation-rotate 1000ms linear infinite; |
|
||||
animation: animation-rotate 1000ms linear infinite; |
|
||||
} |
|
||||
|
|
||||
.loading { |
|
||||
margin-top: 40px; |
|
||||
|
|
||||
h1 { |
|
||||
text-align: center; |
|
||||
margin-top: 25px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.transactionForm { |
|
||||
color: $white; |
|
||||
margin-top: 50px; |
|
||||
|
|
||||
.form { |
|
||||
padding: 0 20px; |
|
||||
} |
|
||||
|
|
||||
.transactionInput { |
|
||||
outline: 0; |
|
||||
border: 0; |
|
||||
border-bottom: 1px solid $secondary; |
|
||||
color: $secondary; |
|
||||
background: transparent; |
|
||||
padding: 5px; |
|
||||
width: 100%; |
|
||||
font-size: 14px; |
|
||||
color: $white; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.routes { |
|
||||
margin-top: 40px; |
|
||||
} |
|
||||
|
|
||||
.route { |
|
||||
margin: 20px 0; |
|
||||
padding: 20px; |
|
||||
cursor: pointer; |
|
||||
|
|
||||
&:hover { |
|
||||
background: darken(#353535, 10%); |
|
||||
} |
|
||||
|
|
||||
&.active { |
|
||||
background: darken(#353535, 10%); |
|
||||
} |
|
||||
|
|
||||
header { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: space-between; |
|
||||
margin-bottom: 20px; |
|
||||
|
|
||||
h1 { |
|
||||
font-size: 16px; |
|
||||
font-weight: bold; |
|
||||
} |
|
||||
|
|
||||
span { |
|
||||
font-weight: bold; |
|
||||
text-transform: uppercase; |
|
||||
font-size: 12px; |
|
||||
letter-spacing: 1.2px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.data { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
|
|
||||
section { |
|
||||
h4 { |
|
||||
font-size: 12px; |
|
||||
font-weight: bold; |
|
||||
margin-bottom: 5px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,196 +0,0 @@ |
|||||
import React, { Component } from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
|
|
||||
import Isvg from 'react-inlinesvg' |
|
||||
import { MdSearch } from 'react-icons/lib/md' |
|
||||
import { FaAngleDown, FaRepeat } from 'react-icons/lib/fa' |
|
||||
|
|
||||
import ContactModal from 'components/Contacts/ContactModal' |
|
||||
import ContactsForm from 'components/Contacts/ContactsForm' |
|
||||
import OnlineContact from 'components/Contacts/OnlineContact' |
|
||||
import PendingContact from 'components/Contacts/PendingContact' |
|
||||
import ClosingContact from 'components/Contacts/ClosingContact' |
|
||||
import OfflineContact from 'components/Contacts/OfflineContact' |
|
||||
import LoadingContact from 'components/Contacts/LoadingContact' |
|
||||
|
|
||||
import plus from 'icons/plus.svg' |
|
||||
|
|
||||
import styles from './Contacts.scss' |
|
||||
|
|
||||
class Contacts extends Component { |
|
||||
constructor(props) { |
|
||||
super(props) |
|
||||
|
|
||||
this.state = { |
|
||||
refreshing: false |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
componentWillMount() { |
|
||||
const { fetchChannels, fetchPeers, fetchDescribeNetwork } = this.props |
|
||||
|
|
||||
fetchChannels() |
|
||||
fetchPeers() |
|
||||
fetchDescribeNetwork() |
|
||||
} |
|
||||
|
|
||||
render() { |
|
||||
const { |
|
||||
channels: { |
|
||||
searchQuery, |
|
||||
filterPulldown, |
|
||||
filter, |
|
||||
loadingChannelPubkeys, |
|
||||
closingChannelIds |
|
||||
}, |
|
||||
currentChannels, |
|
||||
activeChannels, |
|
||||
fetchChannels, |
|
||||
updateChannelSearchQuery, |
|
||||
|
|
||||
toggleFilterPulldown, |
|
||||
changeFilter, |
|
||||
nonActiveFilters, |
|
||||
|
|
||||
openContactsForm, |
|
||||
openContactModal, |
|
||||
|
|
||||
contactModalProps, |
|
||||
contactsFormProps |
|
||||
} = this.props |
|
||||
|
|
||||
console.log('currentChannels: ', currentChannels) |
|
||||
const refreshClicked = () => { |
|
||||
// turn the spinner on
|
|
||||
this.setState({ refreshing: true }) |
|
||||
|
|
||||
// store event in icon so we dont get an error when react clears it
|
|
||||
const icon = this.repeat.childNodes |
|
||||
|
|
||||
// fetch channels
|
|
||||
fetchChannels() |
|
||||
|
|
||||
// wait for the svg to appear as child
|
|
||||
const svgTimeout = setTimeout(() => { |
|
||||
if (icon[0].tagName === 'svg') { |
|
||||
// spin icon for 1 sec
|
|
||||
icon[0].style.animation = 'spin 1000ms linear 1' |
|
||||
clearTimeout(svgTimeout) |
|
||||
} |
|
||||
}, 1) |
|
||||
|
|
||||
// clear animation after the second so we can reuse it
|
|
||||
const refreshTimeout = setTimeout(() => { |
|
||||
icon[0].style.animation = '' |
|
||||
this.setState({ refreshing: false }) |
|
||||
clearTimeout(refreshTimeout) |
|
||||
}, 1000) |
|
||||
} |
|
||||
|
|
||||
return ( |
|
||||
<div className={styles.friendsContainer}> |
|
||||
<ContactModal {...contactModalProps} /> |
|
||||
<ContactsForm {...contactsFormProps} /> |
|
||||
|
|
||||
<header className={styles.header}> |
|
||||
<div className={styles.titleContainer}> |
|
||||
<div className={styles.left}> |
|
||||
<h1>Contacts <span>({activeChannels.length} online)</span></h1> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div className={styles.newFriendContainer}> |
|
||||
<div className={`buttonPrimary ${styles.newFriendButton}`} onClick={openContactsForm}> |
|
||||
<Isvg src={plus} /> |
|
||||
<span>Add</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</header> |
|
||||
|
|
||||
<div className={styles.search}> |
|
||||
<label className={`${styles.label} ${styles.input}`} htmlFor='channelSearch'> |
|
||||
<MdSearch /> |
|
||||
</label> |
|
||||
<input |
|
||||
value={searchQuery} |
|
||||
onChange={event => updateChannelSearchQuery(event.target.value)} |
|
||||
className={`${styles.text} ${styles.input}`} |
|
||||
placeholder='Search your contacts list...' |
|
||||
type='text' |
|
||||
id='channelSearch' |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
<div className={styles.filtersContainer}> |
|
||||
<section> |
|
||||
<h2 onClick={toggleFilterPulldown} className={styles.filterTitle}> |
|
||||
{filter.name} <span className={filterPulldown && styles.pulldown}><FaAngleDown /></span> |
|
||||
</h2> |
|
||||
<ul className={`${styles.filters} ${filterPulldown && styles.active}`}> |
|
||||
{ |
|
||||
nonActiveFilters.map(f => ( |
|
||||
<li key={f.key} onClick={() => changeFilter(f)}> |
|
||||
{f.name} |
|
||||
</li> |
|
||||
)) |
|
||||
} |
|
||||
</ul> |
|
||||
</section> |
|
||||
<section className={styles.refreshContainer}> |
|
||||
<span className={styles.refresh} onClick={refreshClicked} ref={(ref) => { this.repeat = ref }}> |
|
||||
{ |
|
||||
this.state.refreshing ? |
|
||||
<FaRepeat /> |
|
||||
: |
|
||||
'Refresh' |
|
||||
} |
|
||||
</span> |
|
||||
</section> |
|
||||
</div> |
|
||||
|
|
||||
<ul className={`${styles.friends} ${filterPulldown && styles.fade}`}> |
|
||||
{ |
|
||||
loadingChannelPubkeys.map(pubkey => <LoadingContact pubkey={pubkey} isClosing={false} key={pubkey} />) |
|
||||
} |
|
||||
|
|
||||
{ |
|
||||
currentChannels.length > 0 && currentChannels.map((channel, index) => { |
|
||||
if (closingChannelIds.includes(channel.chan_id)) { |
|
||||
return <LoadingContact pubkey={channel.remote_pubkey} isClosing key={index} /> |
|
||||
} else if (Object.prototype.hasOwnProperty.call(channel, 'confirmation_height')) { |
|
||||
return <PendingContact channel={channel} key={index} /> |
|
||||
} else if (Object.prototype.hasOwnProperty.call(channel, 'closing_txid')) { |
|
||||
return <ClosingContact channel={channel} key={index} /> |
|
||||
} else if (!channel.active) { |
|
||||
return <OfflineContact channel={channel} key={index} openContactModal={openContactModal} /> |
|
||||
} |
|
||||
return <OnlineContact channel={channel} key={index} openContactModal={openContactModal} /> |
|
||||
}) |
|
||||
} |
|
||||
</ul> |
|
||||
</div> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Contacts.propTypes = { |
|
||||
fetchPeers: PropTypes.func.isRequired, |
|
||||
fetchDescribeNetwork: PropTypes.func.isRequired, |
|
||||
|
|
||||
channels: PropTypes.object.isRequired, |
|
||||
currentChannels: PropTypes.array.isRequired, |
|
||||
activeChannels: PropTypes.array.isRequired, |
|
||||
fetchChannels: PropTypes.func.isRequired, |
|
||||
updateChannelSearchQuery: PropTypes.func.isRequired, |
|
||||
|
|
||||
toggleFilterPulldown: PropTypes.func.isRequired, |
|
||||
changeFilter: PropTypes.func.isRequired, |
|
||||
nonActiveFilters: PropTypes.array.isRequired, |
|
||||
|
|
||||
openContactsForm: PropTypes.func.isRequired, |
|
||||
openContactModal: PropTypes.func.isRequired, |
|
||||
|
|
||||
contactModalProps: PropTypes.object.isRequired, |
|
||||
contactsFormProps: PropTypes.object.isRequired |
|
||||
} |
|
||||
|
|
||||
export default Contacts |
|
@ -1,172 +0,0 @@ |
|||||
@import '../../../variables.scss'; |
|
||||
|
|
||||
.header { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: space-between; |
|
||||
background: $lightgrey; |
|
||||
|
|
||||
.titleContainer { |
|
||||
padding: 20px 40px; |
|
||||
|
|
||||
.left { |
|
||||
padding: 10px 0; |
|
||||
|
|
||||
h1 { |
|
||||
text-transform: uppercase; |
|
||||
font-size: 26px; |
|
||||
margin-right: 5px; |
|
||||
|
|
||||
span { |
|
||||
display: inline-block; |
|
||||
vertical-align: middle; |
|
||||
font-size: 16px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.newFriendContainer { |
|
||||
padding: 20px 40px; |
|
||||
|
|
||||
.newFriendButton { |
|
||||
box-shadow: none; |
|
||||
transition: all 0.25s; |
|
||||
padding-top: 12px; |
|
||||
padding-bottom: 10px; |
|
||||
font-size: 14px; |
|
||||
|
|
||||
&:hover { |
|
||||
background: darken($main, 10%); |
|
||||
} |
|
||||
|
|
||||
span { |
|
||||
display: inline-block; |
|
||||
vertical-align: top; |
|
||||
|
|
||||
&:nth-child(1) svg { |
|
||||
width: 14px; |
|
||||
height: 14px; |
|
||||
margin-right: 5px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.search { |
|
||||
height: 55px; |
|
||||
padding: 2px 25px; |
|
||||
border-top: 1px solid $darkgrey; |
|
||||
border-bottom: 1px solid $darkgrey; |
|
||||
background: $white; |
|
||||
|
|
||||
.input { |
|
||||
display: inline-block; |
|
||||
vertical-align: top; |
|
||||
height: 100%; |
|
||||
} |
|
||||
|
|
||||
.label { |
|
||||
width: 5%; |
|
||||
line-height: 50px; |
|
||||
font-size: 20px; |
|
||||
text-align: center; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.text { |
|
||||
width: 95%; |
|
||||
outline: 0; |
|
||||
padding: 0; |
|
||||
border: 0; |
|
||||
border-radius: 0; |
|
||||
height: 50px; |
|
||||
font-size: 16px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.filtersContainer { |
|
||||
position: relative; |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: space-between; |
|
||||
margin-top: 20px; |
|
||||
padding: 20px 40px; |
|
||||
|
|
||||
h2, h2 span { |
|
||||
color: $bluegrey; |
|
||||
cursor: pointer; |
|
||||
transition: color 0.25s; |
|
||||
|
|
||||
&:hover { |
|
||||
color: lighten($bluegrey, 10%); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
h2, .filters li { |
|
||||
text-transform: uppercase; |
|
||||
letter-spacing: 1.5px; |
|
||||
color: $darkestgrey; |
|
||||
font-size: 14px; |
|
||||
font-weight: 400; |
|
||||
} |
|
||||
|
|
||||
h2 span.pulldown { |
|
||||
color: $main; |
|
||||
} |
|
||||
|
|
||||
.filters { |
|
||||
display: none; |
|
||||
|
|
||||
&.active { |
|
||||
display: block; |
|
||||
position: absolute; |
|
||||
bottom: -100px; |
|
||||
z-index: 10; |
|
||||
|
|
||||
li { |
|
||||
margin: 5px 0; |
|
||||
cursor: pointer; |
|
||||
|
|
||||
&:hover { |
|
||||
color: $main; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.refreshContainer { |
|
||||
text-align: right; |
|
||||
cursor: pointer; |
|
||||
|
|
||||
.refresh { |
|
||||
cursor: pointer; |
|
||||
color: $darkestgrey; |
|
||||
transition: all 0.25s; |
|
||||
|
|
||||
&:hover { |
|
||||
color: $main; |
|
||||
} |
|
||||
|
|
||||
svg { |
|
||||
font-size: 12px; |
|
||||
color: $darkestgrey; |
|
||||
|
|
||||
&:hover { |
|
||||
color: $darkestgrey; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.friends { |
|
||||
padding: 10px 0 60px 0; |
|
||||
opacity: 1; |
|
||||
transition: all 0.25s; |
|
||||
|
|
||||
&.fade { |
|
||||
opacity: 0.05; |
|
||||
} |
|
||||
} |
|
@ -1,115 +0,0 @@ |
|||||
import { withRouter } from 'react-router' |
|
||||
import { connect } from 'react-redux' |
|
||||
|
|
||||
import { |
|
||||
fetchChannels, |
|
||||
openChannel, |
|
||||
closeChannel, |
|
||||
|
|
||||
updateChannelSearchQuery, |
|
||||
toggleFilterPulldown, |
|
||||
changeFilter, |
|
||||
openContactModal, |
|
||||
closeContactModal, |
|
||||
currentChannels, |
|
||||
|
|
||||
channelsSelectors |
|
||||
} from 'reducers/channels' |
|
||||
|
|
||||
import { fetchPeers } from 'reducers/peers' |
|
||||
|
|
||||
import { fetchDescribeNetwork } from 'reducers/network' |
|
||||
|
|
||||
import { |
|
||||
openContactsForm, |
|
||||
closeContactsForm, |
|
||||
updateContactFormSearchQuery, |
|
||||
updateManualFormSearchQuery, |
|
||||
updateContactCapacity, |
|
||||
contactFormSelectors, |
|
||||
updateManualFormErrors |
|
||||
} from 'reducers/contactsform' |
|
||||
|
|
||||
import Contacts from '../components/Contacts' |
|
||||
|
|
||||
const mapDispatchToProps = { |
|
||||
openContactsForm, |
|
||||
closeContactsForm, |
|
||||
openContactModal, |
|
||||
closeContactModal, |
|
||||
updateContactFormSearchQuery, |
|
||||
updateManualFormSearchQuery, |
|
||||
updateContactCapacity, |
|
||||
openChannel, |
|
||||
closeChannel, |
|
||||
updateChannelSearchQuery, |
|
||||
toggleFilterPulldown, |
|
||||
changeFilter, |
|
||||
updateManualFormErrors, |
|
||||
fetchChannels, |
|
||||
fetchPeers, |
|
||||
fetchDescribeNetwork |
|
||||
} |
|
||||
|
|
||||
const mapStateToProps = state => ({ |
|
||||
channels: state.channels, |
|
||||
peers: state.peers, |
|
||||
network: state.network, |
|
||||
contactsform: state.contactsform, |
|
||||
|
|
||||
currentChannels: currentChannels(state), |
|
||||
activeChannels: channelsSelectors.activeChannels(state), |
|
||||
activeChannelPubkeys: channelsSelectors.activeChannelPubkeys(state), |
|
||||
nonActiveChannels: channelsSelectors.nonActiveChannels(state), |
|
||||
nonActiveChannelPubkeys: channelsSelectors.nonActiveChannelPubkeys(state), |
|
||||
pendingOpenChannels: channelsSelectors.pendingOpenChannels(state), |
|
||||
pendingOpenChannelPubkeys: channelsSelectors.pendingOpenChannelPubkeys(state), |
|
||||
closingPendingChannels: channelsSelectors.closingPendingChannels(state), |
|
||||
nonActiveFilters: channelsSelectors.nonActiveFilters(state), |
|
||||
channelNodes: channelsSelectors.channelNodes(state), |
|
||||
|
|
||||
filteredNetworkNodes: contactFormSelectors.filteredNetworkNodes(state), |
|
||||
showManualForm: contactFormSelectors.showManualForm(state), |
|
||||
manualFormIsValid: contactFormSelectors.manualFormIsValid(state) |
|
||||
}) |
|
||||
|
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => { |
|
||||
const contactModalProps = { |
|
||||
closeContactModal: dispatchProps.closeContactModal, |
|
||||
closeChannel: dispatchProps.closeChannel, |
|
||||
|
|
||||
isOpen: stateProps.channels.contactModal.isOpen, |
|
||||
channel: stateProps.channels.contactModal.channel, |
|
||||
channelNodes: stateProps.channelNodes, |
|
||||
closingChannelIds: stateProps.channels.closingChannelIds, |
|
||||
manualFormIsValid: stateProps.manualFormIsValid |
|
||||
} |
|
||||
|
|
||||
const contactsFormProps = { |
|
||||
closeContactsForm: dispatchProps.closeContactsForm, |
|
||||
updateContactFormSearchQuery: dispatchProps.updateContactFormSearchQuery, |
|
||||
updateManualFormSearchQuery: dispatchProps.updateManualFormSearchQuery, |
|
||||
updateContactCapacity: dispatchProps.updateContactCapacity, |
|
||||
openChannel: dispatchProps.openChannel, |
|
||||
contactsform: stateProps.contactsform, |
|
||||
filteredNetworkNodes: stateProps.filteredNetworkNodes, |
|
||||
loadingChannelPubkeys: stateProps.channels.loadingChannelPubkeys, |
|
||||
showManualForm: stateProps.showManualForm, |
|
||||
manualFormIsValid: stateProps.manualFormIsValid, |
|
||||
activeChannelPubkeys: stateProps.activeChannelPubkeys, |
|
||||
nonActiveChannelPubkeys: stateProps.nonActiveChannelPubkeys, |
|
||||
pendingOpenChannelPubkeys: stateProps.pendingOpenChannelPubkeys, |
|
||||
updateManualFormErrors: dispatchProps.updateManualFormErrors |
|
||||
} |
|
||||
|
|
||||
return { |
|
||||
...stateProps, |
|
||||
...dispatchProps, |
|
||||
...ownProps, |
|
||||
|
|
||||
contactModalProps, |
|
||||
contactsFormProps |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(Contacts)) |
|
@ -1,3 +0,0 @@ |
|||||
import ContactsContainer from './containers/ContactsContainer' |
|
||||
|
|
||||
export default ContactsContainer |
|
@ -1,88 +0,0 @@ |
|||||
import { shell } from 'electron' |
|
||||
import React, { Component } from 'react' |
|
||||
|
|
||||
import { MdSearch } from 'react-icons/lib/md' |
|
||||
|
|
||||
import styles from './Help.scss' |
|
||||
|
|
||||
class Help extends Component { |
|
||||
constructor(props) { |
|
||||
super(props) |
|
||||
|
|
||||
this.state = { |
|
||||
videos: [ |
|
||||
{ |
|
||||
id: '8kZq6eec49A', |
|
||||
title: 'Syncing and Depositing - Zap Lightning Network Wallet Tutorial (Video 1)' |
|
||||
}, |
|
||||
{ |
|
||||
id: 'xSiTH63fOQM', |
|
||||
title: 'Adding a contact - Zap Lightning Network Wallet Tutorial (Video 2)' |
|
||||
}, |
|
||||
{ |
|
||||
id: 'c0SLmywYDHU', |
|
||||
title: 'Making a Lightning Network payment - Zap Lightning Network Wallet Tutorial (Video 3)' |
|
||||
}, |
|
||||
{ |
|
||||
id: 'Xrx2TiiF90Q', |
|
||||
title: 'Receive Lightning Network payment - Zap Lightning Network Wallet Tutorial (Video 4)' |
|
||||
}, |
|
||||
{ |
|
||||
id: 'YfxukBHnwUM', |
|
||||
title: 'Network Map - Zap Lightning Network Wallet Tutorial (Video 5)' |
|
||||
}, |
|
||||
{ |
|
||||
id: 'NORklrrYzOg', |
|
||||
title: 'Using an explorer to add Zap contacts - Zap Lightning Network Wallet Tutorial (Video 6)' |
|
||||
} |
|
||||
], |
|
||||
searchQuery: '' |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
render() { |
|
||||
const { videos, searchQuery } = this.state |
|
||||
const filteredVideos = videos.filter(video => video.title.includes(searchQuery)) |
|
||||
|
|
||||
return ( |
|
||||
<div className={styles.helpContainer}> |
|
||||
<header className={styles.header}> |
|
||||
<h1>Video tutorials</h1> |
|
||||
</header> |
|
||||
|
|
||||
<div className={styles.search}> |
|
||||
<label className={`${styles.label} ${styles.input}`} htmlFor='helpSearch'> |
|
||||
<MdSearch /> |
|
||||
</label> |
|
||||
<input |
|
||||
value={searchQuery} |
|
||||
onChange={event => this.setState({ searchQuery: event.target.value })} |
|
||||
className={`${styles.text} ${styles.input}`} |
|
||||
placeholder='Search the video library...' |
|
||||
type='text' |
|
||||
id='helpSearch' |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
<ul className={styles.videos}> |
|
||||
{ |
|
||||
filteredVideos.map((video, index) => ( |
|
||||
<li key={index}> |
|
||||
<iframe |
|
||||
src={`https://www.youtube.com/embed/${video.id}`} |
|
||||
frameBorder='0' |
|
||||
title={video.id} |
|
||||
/> |
|
||||
<section className={styles.info} onClick={() => shell.openExternal(`https://www.youtube.com/watch?v=${video.id}`)}> |
|
||||
<h2>{video.title}</h2> |
|
||||
</section> |
|
||||
</li> |
|
||||
)) |
|
||||
} |
|
||||
</ul> |
|
||||
</div> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default Help |
|
@ -1,90 +0,0 @@ |
|||||
@import '../../../variables.scss'; |
|
||||
|
|
||||
.header { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: space-between; |
|
||||
background: $lightgrey; |
|
||||
padding: 20px 40px; |
|
||||
|
|
||||
h1 { |
|
||||
text-transform: uppercase; |
|
||||
font-size: 26px; |
|
||||
margin-right: 5px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.search { |
|
||||
height: 55px; |
|
||||
padding: 2px 25px; |
|
||||
border-top: 1px solid $darkgrey; |
|
||||
border-bottom: 1px solid $darkgrey; |
|
||||
background: $white; |
|
||||
|
|
||||
.input { |
|
||||
display: inline-block; |
|
||||
vertical-align: top; |
|
||||
height: 100%; |
|
||||
} |
|
||||
|
|
||||
.label { |
|
||||
width: 5%; |
|
||||
line-height: 50px; |
|
||||
font-size: 20px; |
|
||||
text-align: center; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.text { |
|
||||
width: 95%; |
|
||||
outline: 0; |
|
||||
padding: 0; |
|
||||
border: 0; |
|
||||
border-radius: 0; |
|
||||
height: 50px; |
|
||||
font-size: 16px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.videos { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
flex-wrap: wrap; |
|
||||
justify-content: space-around; |
|
||||
padding: 20px 40px; |
|
||||
|
|
||||
li { |
|
||||
position: relative; |
|
||||
width: 50%; |
|
||||
text-align: center; |
|
||||
margin: 20px 0; |
|
||||
width: 400px; |
|
||||
height: 400px; |
|
||||
transition: all 0.25s; |
|
||||
|
|
||||
&:hover { |
|
||||
opacity: 0.5; |
|
||||
|
|
||||
.info { |
|
||||
padding: 50px 15px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
iframe { |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
} |
|
||||
|
|
||||
.info { |
|
||||
position: absolute; |
|
||||
bottom: 0; |
|
||||
width: calc(100% - 30px); |
|
||||
padding: 15px; |
|
||||
background: $darkgrey; |
|
||||
cursor: pointer; |
|
||||
transition: all 0.25s; |
|
||||
text-align: left; |
|
||||
line-height: 1.5; |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,10 +0,0 @@ |
|||||
import { withRouter } from 'react-router' |
|
||||
import { connect } from 'react-redux' |
|
||||
|
|
||||
import Help from '../components/Help' |
|
||||
|
|
||||
const mapDispatchToProps = {} |
|
||||
|
|
||||
const mapStateToProps = () => ({}) |
|
||||
|
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Help)) |
|
@ -1,3 +0,0 @@ |
|||||
import HelpContainer from './containers/HelpContainer' |
|
||||
|
|
||||
export default HelpContainer |
|
@ -1,168 +0,0 @@ |
|||||
import React, { Component } from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
|
|
||||
import CanvasNetworkGraph from 'components/Network/CanvasNetworkGraph' |
|
||||
import PeersList from 'components/Network/PeersList' |
|
||||
import ChannelsList from 'components/Network/ChannelsList' |
|
||||
import TransactionForm from 'components/Network/TransactionForm' |
|
||||
|
|
||||
import styles from './Network.scss' |
|
||||
|
|
||||
class Network extends Component { |
|
||||
componentWillMount() { |
|
||||
const { fetchDescribeNetwork, fetchPeers, fetchChannels } = this.props |
|
||||
|
|
||||
fetchPeers() |
|
||||
fetchChannels() |
|
||||
fetchDescribeNetwork() |
|
||||
} |
|
||||
|
|
||||
componentDidUpdate(prevProps) { |
|
||||
const { |
|
||||
payReqIsLn, network: { pay_req }, fetchInvoiceAndQueryRoutes, clearQueryRoutes |
|
||||
} = this.props |
|
||||
|
|
||||
// If LN go retrieve invoice details
|
|
||||
if ((prevProps.network.pay_req !== pay_req) && payReqIsLn) { |
|
||||
fetchInvoiceAndQueryRoutes(pay_req) |
|
||||
} |
|
||||
|
|
||||
if (prevProps.payReqIsLn && !payReqIsLn) { |
|
||||
clearQueryRoutes() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
componentWillUnmount() { |
|
||||
const { |
|
||||
clearQueryRoutes, resetPayReq, clearSelectedChannels, clearSelectedPeers |
|
||||
} = this.props |
|
||||
|
|
||||
clearQueryRoutes() |
|
||||
resetPayReq() |
|
||||
clearSelectedChannels() |
|
||||
clearSelectedPeers() |
|
||||
} |
|
||||
|
|
||||
render() { |
|
||||
const { |
|
||||
setCurrentTab, |
|
||||
updateSelectedPeers, |
|
||||
setCurrentRoute, |
|
||||
|
|
||||
network, |
|
||||
selectedPeerPubkeys, |
|
||||
currentRouteChanIds, |
|
||||
|
|
||||
peers: { peers }, |
|
||||
|
|
||||
activeChannels, |
|
||||
selectedChannelIds, |
|
||||
updateSelectedChannels, |
|
||||
|
|
||||
updatePayReq, |
|
||||
|
|
||||
identity_pubkey |
|
||||
} = this.props |
|
||||
|
|
||||
const renderContent = () => { |
|
||||
switch (network.currentTab) { |
|
||||
case 1: |
|
||||
return <PeersList peers={peers} updateSelectedPeers={updateSelectedPeers} selectedPeerPubkeys={selectedPeerPubkeys} /> |
|
||||
case 2: |
|
||||
return <ChannelsList channels={activeChannels} updateSelectedChannels={updateSelectedChannels} selectedChannelIds={selectedChannelIds} /> |
|
||||
case 3: |
|
||||
return ( |
|
||||
<TransactionForm |
|
||||
updatePayReq={updatePayReq} |
|
||||
pay_req={network.pay_req} |
|
||||
loadingRoutes={network.fetchingInvoiceAndQueryingRoutes} |
|
||||
payReqRoutes={network.payReqRoutes} |
|
||||
setCurrentRoute={setCurrentRoute} |
|
||||
currentRoute={network.currentRoute} |
|
||||
/> |
|
||||
) |
|
||||
default: |
|
||||
return <span /> |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return ( |
|
||||
<div className={styles.container}> |
|
||||
{ |
|
||||
!network.networkLoading && |
|
||||
<section className={styles.stats}> |
|
||||
<span>{network.nodes.length} nodes</span> |
|
||||
<span>|</span> |
|
||||
<span>{network.edges.length} channels</span> |
|
||||
</section> |
|
||||
} |
|
||||
|
|
||||
<CanvasNetworkGraph |
|
||||
className={styles.network} |
|
||||
network={network} |
|
||||
identity_pubkey={identity_pubkey} |
|
||||
selectedPeerPubkeys={selectedPeerPubkeys} |
|
||||
selectedChannelIds={selectedChannelIds} |
|
||||
currentRouteChanIds={currentRouteChanIds} |
|
||||
/> |
|
||||
|
|
||||
<section className={styles.toolbox}> |
|
||||
<ul className={styles.tabs}> |
|
||||
<li |
|
||||
className={`${styles.tab} ${styles.peersTab} ${network.currentTab === 1 && styles.active}`} |
|
||||
onClick={() => setCurrentTab(1)} |
|
||||
> |
|
||||
Peers |
|
||||
</li> |
|
||||
<li |
|
||||
className={`${styles.tab} ${styles.channelsTab} ${network.currentTab === 2 && styles.active}`} |
|
||||
onClick={() => setCurrentTab(2)} |
|
||||
> |
|
||||
Channels |
|
||||
</li> |
|
||||
<li |
|
||||
className={`${styles.tab} ${styles.transactionsTab} ${network.currentTab === 3 && styles.active}`} |
|
||||
onClick={() => setCurrentTab(3)} |
|
||||
> |
|
||||
Transactions |
|
||||
</li> |
|
||||
</ul> |
|
||||
|
|
||||
<div className={styles.content}> |
|
||||
{renderContent()} |
|
||||
</div> |
|
||||
</section> |
|
||||
</div> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Network.propTypes = { |
|
||||
fetchDescribeNetwork: PropTypes.func.isRequired, |
|
||||
fetchPeers: PropTypes.func.isRequired, |
|
||||
setCurrentTab: PropTypes.func.isRequired, |
|
||||
fetchChannels: PropTypes.func.isRequired, |
|
||||
fetchInvoiceAndQueryRoutes: PropTypes.func.isRequired, |
|
||||
clearQueryRoutes: PropTypes.func.isRequired, |
|
||||
resetPayReq: PropTypes.func.isRequired, |
|
||||
clearSelectedChannels: PropTypes.func.isRequired, |
|
||||
clearSelectedPeers: PropTypes.func.isRequired, |
|
||||
updateSelectedPeers: PropTypes.func.isRequired, |
|
||||
setCurrentRoute: PropTypes.func.isRequired, |
|
||||
updateSelectedChannels: PropTypes.func.isRequired, |
|
||||
updatePayReq: PropTypes.func.isRequired, |
|
||||
|
|
||||
network: PropTypes.object.isRequired, |
|
||||
peers: PropTypes.object.isRequired, |
|
||||
|
|
||||
selectedPeerPubkeys: PropTypes.array.isRequired, |
|
||||
currentRouteChanIds: PropTypes.array.isRequired, |
|
||||
activeChannels: PropTypes.array.isRequired, |
|
||||
selectedChannelIds: PropTypes.array.isRequired, |
|
||||
|
|
||||
identity_pubkey: PropTypes.string.isRequired, |
|
||||
|
|
||||
payReqIsLn: PropTypes.bool.isRequired |
|
||||
} |
|
||||
|
|
||||
export default Network |
|
@ -1,99 +0,0 @@ |
|||||
@import '../../../variables.scss'; |
|
||||
|
|
||||
@keyframes dash { |
|
||||
to { |
|
||||
stroke-dashoffset: 1000; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@keyframes fadein { |
|
||||
0% { background: $white; } |
|
||||
50% { background: lighten($secondary, 50%); } |
|
||||
100% { background: $secondary; animation-fill-mode:forwards; } |
|
||||
} |
|
||||
|
|
||||
.container { |
|
||||
width: 100%; |
|
||||
height: 100vh; |
|
||||
animation: fadein 0.5s; |
|
||||
animation-timing-function:linear; |
|
||||
animation-fill-mode:forwards; |
|
||||
animation-iteration-count: 1; |
|
||||
|
|
||||
line.active { |
|
||||
opacity: 1; |
|
||||
stroke: green; |
|
||||
stroke-width: 5; |
|
||||
stroke-dasharray: 100; |
|
||||
animation: dash 2.5s infinite linear; |
|
||||
} |
|
||||
|
|
||||
circle { |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.stats { |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
right: 30%; |
|
||||
padding: 20px; |
|
||||
|
|
||||
span { |
|
||||
color: $main; |
|
||||
margin: 0 2.5px; |
|
||||
line-height: 25px; |
|
||||
vertical-align: middle; |
|
||||
|
|
||||
&:nth-child(2) { |
|
||||
font-size: 25px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.network, .toolbox { |
|
||||
display: inline-block; |
|
||||
vertical-align: top; |
|
||||
height: 100vh; |
|
||||
} |
|
||||
|
|
||||
.network { |
|
||||
width: 70%; |
|
||||
} |
|
||||
|
|
||||
.toolbox { |
|
||||
width: 30%; |
|
||||
height: 100%; |
|
||||
background: #353535; |
|
||||
overflow-y: scroll; |
|
||||
} |
|
||||
|
|
||||
.tabs { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
padding-top: 40px; |
|
||||
|
|
||||
.tab { |
|
||||
color: $white; |
|
||||
text-align: center; |
|
||||
cursor: pointer; |
|
||||
width: 100%; |
|
||||
padding: 10px 0; |
|
||||
border-bottom: 1px solid #464646; |
|
||||
transition: all 0.5s; |
|
||||
|
|
||||
&.peersTab:hover, &.peersTab.active { |
|
||||
border-bottom: 1px solid #588CF0; |
|
||||
} |
|
||||
|
|
||||
&.channelsTab:hover, &.channelsTab.active { |
|
||||
border-bottom: 1px solid #88D4A2; |
|
||||
} |
|
||||
|
|
||||
&.transactionsTab:hover, &.transactionsTab.active { |
|
||||
border-bottom: 1px solid #FFDC53; |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,62 +0,0 @@ |
|||||
import { withRouter } from 'react-router' |
|
||||
import { connect } from 'react-redux' |
|
||||
|
|
||||
import { |
|
||||
fetchDescribeNetwork, |
|
||||
|
|
||||
setCurrentTab, |
|
||||
|
|
||||
updateSelectedPeers, |
|
||||
clearSelectedPeers, |
|
||||
|
|
||||
updateSelectedChannels, |
|
||||
clearSelectedChannels, |
|
||||
|
|
||||
setCurrentRoute, |
|
||||
|
|
||||
updatePayReq, |
|
||||
resetPayReq, |
|
||||
|
|
||||
fetchInvoiceAndQueryRoutes, |
|
||||
clearQueryRoutes, |
|
||||
|
|
||||
networkSelectors |
|
||||
} from '../../../reducers/network' |
|
||||
import { fetchPeers } from '../../../reducers/peers' |
|
||||
import { fetchChannels, channelsSelectors } from '../../../reducers/channels' |
|
||||
|
|
||||
import Network from '../components/Network' |
|
||||
|
|
||||
const mapDispatchToProps = { |
|
||||
fetchDescribeNetwork, |
|
||||
setCurrentTab, |
|
||||
|
|
||||
updateSelectedPeers, |
|
||||
clearSelectedPeers, |
|
||||
|
|
||||
updatePayReq, |
|
||||
fetchInvoiceAndQueryRoutes, |
|
||||
setCurrentRoute, |
|
||||
clearQueryRoutes, |
|
||||
resetPayReq, |
|
||||
|
|
||||
fetchPeers, |
|
||||
|
|
||||
fetchChannels, |
|
||||
updateSelectedChannels, |
|
||||
clearSelectedChannels |
|
||||
} |
|
||||
|
|
||||
const mapStateToProps = state => ({ |
|
||||
network: state.network, |
|
||||
peers: state.peers, |
|
||||
identity_pubkey: state.info.data.identity_pubkey, |
|
||||
|
|
||||
selectedPeerPubkeys: networkSelectors.selectedPeerPubkeys(state), |
|
||||
selectedChannelIds: networkSelectors.selectedChannelIds(state), |
|
||||
payReqIsLn: networkSelectors.payReqIsLn(state), |
|
||||
currentRouteChanIds: networkSelectors.currentRouteChanIds(state), |
|
||||
activeChannels: channelsSelectors.activeChannels(state) |
|
||||
}) |
|
||||
|
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Network)) |
|
@ -1,3 +0,0 @@ |
|||||
import NetworkContainer from './containers/NetworkContainer' |
|
||||
|
|
||||
export default NetworkContainer |
|
Loading…
Reference in new issue