16 changed files with 507 additions and 77 deletions
@ -1,15 +0,0 @@ |
|||||
import React from 'react' |
|
||||
import PropTypes from 'prop-types' |
|
||||
import styles from './CardChannel.scss' |
|
||||
|
|
||||
const CardChannel = ({ channel }) => ( |
|
||||
<li className={styles.channel}> |
|
||||
{channel.chan_id} |
|
||||
</li> |
|
||||
) |
|
||||
|
|
||||
CardChannel.propTypes = { |
|
||||
channel: PropTypes.object.isRequired |
|
||||
} |
|
||||
|
|
||||
export default CardChannel |
|
@ -1,6 +0,0 @@ |
|||||
.channel { |
|
||||
width: 40%; |
|
||||
margin: 0 5% 20px 5%; |
|
||||
height: 500px; |
|
||||
background: red; |
|
||||
} |
|
@ -0,0 +1,133 @@ |
|||||
|
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 |
@ -0,0 +1,114 @@ |
|||||
|
@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; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,129 @@ |
|||||
|
import { createSelector } from 'reselect' |
||||
|
import { ipcRenderer } from 'electron' |
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Constants
|
||||
|
// ------------------------------------
|
||||
|
export const GET_DESCRIBE_NETWORK = 'GET_DESCRIBE_NETWORK' |
||||
|
export const RECEIVE_DESCRIBE_NETWORK = 'RECEIVE_DESCRIBE_NETWORK' |
||||
|
|
||||
|
export const GET_QUERY_ROUTES = 'GET_QUERY_ROUTES' |
||||
|
export const RECEIVE_QUERY_ROUTES = 'RECEIVE_QUERY_ROUTES' |
||||
|
|
||||
|
export const SET_CURRENT_ROUTE = 'SET_CURRENT_ROUTE' |
||||
|
|
||||
|
export const SET_CURRENT_CHANNEL = 'SET_CURRENT_CHANNEL' |
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Actions
|
||||
|
// ------------------------------------
|
||||
|
export function getDescribeNetwork() { |
||||
|
return { |
||||
|
type: GET_DESCRIBE_NETWORK |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function getQueryRoutes(pubkey) { |
||||
|
return { |
||||
|
type: GET_QUERY_ROUTES, |
||||
|
pubkey |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function setCurrentRoute(route) { |
||||
|
return { |
||||
|
type: SET_CURRENT_ROUTE, |
||||
|
route |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function setCurrentChannel(selectedChannel) { |
||||
|
return { |
||||
|
type: SET_CURRENT_CHANNEL, |
||||
|
selectedChannel |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Send IPC event for describeNetwork
|
||||
|
export const fetchDescribeNetwork = () => (dispatch) => { |
||||
|
dispatch(getDescribeNetwork()) |
||||
|
ipcRenderer.send('lnd', { msg: 'describeNetwork' }) |
||||
|
} |
||||
|
|
||||
|
// Receive IPC event for describeNetwork
|
||||
|
export const receiveDescribeNetwork = (event, { nodes, edges }) => dispatch => dispatch({ type: RECEIVE_DESCRIBE_NETWORK, nodes, edges }) |
||||
|
|
||||
|
export const queryRoutes = (pubkey, amount) => (dispatch) => { |
||||
|
dispatch(getQueryRoutes(pubkey)) |
||||
|
ipcRenderer.send('lnd', { msg: 'queryRoutes', data: { pubkey, amount } }) |
||||
|
} |
||||
|
|
||||
|
export const receiveQueryRoutes = (event, { routes }) => dispatch => dispatch({ type: RECEIVE_QUERY_ROUTES, routes }) |
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Action Handlers
|
||||
|
// ------------------------------------
|
||||
|
const ACTION_HANDLERS = { |
||||
|
[GET_DESCRIBE_NETWORK]: state => ({ ...state, networkLoading: true }), |
||||
|
[RECEIVE_DESCRIBE_NETWORK]: (state, { nodes, edges }) => ({ ...state, networkLoading: false, nodes, edges }), |
||||
|
|
||||
|
[GET_QUERY_ROUTES]: (state, { pubkey }) => ({ ...state, networkLoading: true, selectedNode: { pubkey, routes: [], currentRoute: {} } }), |
||||
|
[RECEIVE_QUERY_ROUTES]: (state, { routes }) => ( |
||||
|
{ |
||||
|
...state, |
||||
|
networkLoading: false, |
||||
|
selectedNode: { pubkey: state.selectedNode.pubkey, routes, currentRoute: routes[0] } |
||||
|
} |
||||
|
), |
||||
|
|
||||
|
[SET_CURRENT_ROUTE]: (state, { route }) => ( |
||||
|
{ |
||||
|
...state, |
||||
|
selectedNode: { pubkey: state.selectedNode.pubkey, routes: state.selectedNode.routes, currentRoute: route } |
||||
|
} |
||||
|
), |
||||
|
|
||||
|
[SET_CURRENT_CHANNEL]: (state, { selectedChannel }) => ({ ...state, selectedChannel }) |
||||
|
} |
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Selectors
|
||||
|
// ------------------------------------
|
||||
|
const networkSelectors = {} |
||||
|
const currentRouteSelector = state => state.network.selectedNode.currentRoute |
||||
|
|
||||
|
networkSelectors.currentRouteHopChanIds = createSelector( |
||||
|
currentRouteSelector, |
||||
|
(currentRoute) => { |
||||
|
if (!currentRoute.hops) { return [] } |
||||
|
|
||||
|
return currentRoute.hops.map(hop => hop.chan_id) |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
export { networkSelectors } |
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Initial State
|
||||
|
// ------------------------------------
|
||||
|
const initialState = { |
||||
|
networkLoading: false, |
||||
|
nodes: [], |
||||
|
edges: [], |
||||
|
selectedNode: { |
||||
|
pubkey: '', |
||||
|
routes: [], |
||||
|
currentRoute: {} |
||||
|
}, |
||||
|
selectedChannel: {} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// ------------------------------------
|
||||
|
// Reducer
|
||||
|
// ------------------------------------
|
||||
|
export default function activityReducer(state = initialState, action) { |
||||
|
const handler = ACTION_HANDLERS[action.type] |
||||
|
|
||||
|
return handler ? handler(state, action) : state |
||||
|
} |
Loading…
Reference in new issue