import React, { Component } from 'react' import PropTypes from 'prop-types' import find from 'lodash/find' import Isvg from 'react-inlinesvg' import FaExternalLink from 'react-icons/lib/fa/external-link' import FaCircle from 'react-icons/lib/fa/circle' import FaRepeat from 'react-icons/lib/fa/repeat' import FaAngleDown from 'react-icons/lib/fa/angle-down' import { btc, blockExplorer } from 'utils' import plus from 'icons/plus.svg' import search from 'icons/search.svg' import Value from 'components/Value' import SuggestedNodes from './SuggestedNodes' import styles from './Network.scss' class Network extends Component { constructor(props) { super(props) this.state = { refreshing: false } } render() { const { channels: { searchQuery, filterPulldown, filter, selectedChannel, loadingChannelPubkeys, closingChannelIds }, currentChannels, balance, ticker, currentTicker, nodes, fetchChannels, openContactsForm, nonActiveFilters, toggleFilterPulldown, changeFilter, updateChannelSearchQuery, setSelectedChannel, closeChannel, suggestedNodesProps, network, currencyName } = this.props 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) } // when the user clicks the action to close the channel const removeClicked = removeChannel => { closeChannel({ channel_point: removeChannel.channel_point, chan_id: removeChannel.chan_id, force: !removeChannel.active }) } // when a user clicks a channel const channelClicked = clickedChannel => { // selectedChannel === channel ? setSelectedChannel(null) : setSelectedChannel(channel) if (selectedChannel === clickedChannel) { setSelectedChannel(null) } else { setSelectedChannel(clickedChannel) } } const displayNodeName = displayedChannel => { const node = find(nodes, n => displayedChannel.remote_pubkey === n.pub_key) if (node && node.alias.length) { return node.alias } return displayedChannel.remote_pubkey ? displayedChannel.remote_pubkey.substring(0, 10) : displayedChannel.remote_node_pub.substring(0, 10) } const channelStatus = statusChannel => { // if the channel has a confirmation_height property that means it's pending if (Object.prototype.hasOwnProperty.call(statusChannel, 'confirmation_height')) { return 'pending' } // if the channel has a closing tx that means it's closing if (Object.prototype.hasOwnProperty.call(statusChannel, 'closing_txid')) { return 'closing' } // if the channel is in waiting_close_channels phase if (Object.prototype.hasOwnProperty.call(statusChannel, 'waiting_close_channels')) { return 'closing' } // if we are in the process of closing this channel if (closingChannelIds.includes(statusChannel.chan_id)) { return 'closing' } // if the channel isn't active that means the remote peer isn't online if (!statusChannel.active) { return 'offline' } // if all of the above conditionals fail we can assume the node is online :) return 'online' } const usdAmount = btc.satoshisToUsd(balance.channelBalance, currentTicker.price_usd) return (

My Network

{btc.satoshisToBtc(balance.channelBalance)} {currencyName} ≈ ${usdAmount ? usdAmount.toLocaleString() : ''}
{!loadingChannelPubkeys.length && !currentChannels.length && !searchQuery.length && } {(loadingChannelPubkeys.length || currentChannels.length) && (

{filter.name}{' '}

    {nonActiveFilters.map(f => (
  • changeFilter(f)}> {f.name}
  • ))}
{ this.repeat = ref }} > {this.state.refreshing ? : 'Refresh'}
)}
    {loadingChannelPubkeys.length && loadingChannelPubkeys.map(loadingPubkey => { // TODO(jimmymow): refactor this out. same logic is in displayNodeName above const node = find(nodes, n => loadingPubkey === n.pub_key) const nodeDisplay = () => { if (node && node.alias.length) { return node.alias } return loadingPubkey.substring(0, 10) } return (
  • {nodeDisplay()}
  • ) })} {currentChannels.length && currentChannels.map((channelObj, index) => { const channel = Object.prototype.hasOwnProperty.call(channelObj, 'channel') ? channelObj.channel : channelObj const pubkey = channel.remote_node_pub || channel.remote_pubkey return (
  • channelClicked(channel)} >
    {closingChannelIds.includes(channel.chan_id) ? ( ) : ( )} {displayNodeName(channel)} {selectedChannel === channel && ( blockExplorer.showTransaction( network, channel.channel_point.split(':')[0] ) } > )}

    {`${pubkey.substring(0, 30)}...`}

    Pay Limit

    {currencyName}

    Request Limit

    {ticker.currency.toUpperCase()}

    {closingChannelIds.includes(channel.chan_id) && (
    Closing
    )} {Object.prototype.hasOwnProperty.call(channel, 'active') && !closingChannelIds.includes(channel.chan_id) && (
    removeClicked(channel)}>
    Disconnect
    )}
  • ) })}
{(loadingChannelPubkeys.length || currentChannels.length || searchQuery.length) && ( )}
) } } Network.propTypes = { currentChannels: PropTypes.array.isRequired, nodes: PropTypes.array.isRequired, nonActiveFilters: PropTypes.array.isRequired, channels: PropTypes.object.isRequired, balance: PropTypes.object.isRequired, currentTicker: PropTypes.object.isRequired, ticker: PropTypes.object.isRequired, suggestedNodesProps: PropTypes.object.isRequired, network: PropTypes.object.isRequired, fetchChannels: PropTypes.func.isRequired, openContactsForm: PropTypes.func.isRequired, toggleFilterPulldown: PropTypes.func.isRequired, changeFilter: PropTypes.func.isRequired, updateChannelSearchQuery: PropTypes.func.isRequired, setSelectedChannel: PropTypes.func.isRequired, closeChannel: PropTypes.func.isRequired, currencyName: PropTypes.string.isRequired } export default Network