@ -1,6 +1,7 @@ |
|||
import React from 'react' |
|||
import Isvg from 'react-inlinesvg' |
|||
import checkmarkIcon from 'components/AnimatedCheckmark/checkmark.svg' |
|||
|
|||
const AnimatedCheckmark = () => <Isvg src={'./components/AnimatedCheckmark/checkmark.svg'} /> |
|||
const AnimatedCheckmark = () => <Isvg src={checkmarkIcon} /> |
|||
|
|||
export default AnimatedCheckmark |
|||
|
@ -0,0 +1,90 @@ |
|||
import React from 'react' |
|||
import PropTypes from 'prop-types' |
|||
import ReactModal from 'react-modal' |
|||
|
|||
import { FaClose } from 'react-icons/lib/fa' |
|||
|
|||
import StepOne from './StepOne' |
|||
import StepTwo from './StepTwo' |
|||
import StepThree from './StepThree' |
|||
import StepFour from './StepFour' |
|||
import Footer from './Footer' |
|||
|
|||
import styles from './ChannelForm.scss' |
|||
|
|||
const ChannelForm = ({ |
|||
channelform, |
|||
openChannel, |
|||
closeChannelForm, |
|||
changeStep, |
|||
setNodeKey, |
|||
setLocalAmount, |
|||
setPushAmount, |
|||
channelFormHeader, |
|||
channelFormProgress, |
|||
stepTwoIsValid, |
|||
peers |
|||
}) => { |
|||
const renderStep = () => { |
|||
const { step } = channelform |
|||
|
|||
switch (step) { |
|||
case 1: |
|||
return <StepOne peers={peers} changeStep={changeStep} setNodeKey={setNodeKey} /> |
|||
case 2: |
|||
return <StepTwo local_amt={channelform.local_amt} setLocalAmount={setLocalAmount} /> |
|||
case 3: |
|||
return <StepThree push_amt={channelform.push_amt} setPushAmount={setPushAmount} /> |
|||
default: |
|||
return <StepFour node_key={channelform.node_key} local_amt={channelform.local_amt} push_amt={channelform.push_amt} /> |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<ReactModal |
|||
isOpen={channelform.isOpen} |
|||
ariaHideApp |
|||
shouldCloseOnOverlayClick |
|||
contentLabel='No Overlay Click Modal' |
|||
onRequestClose={closeChannelForm} |
|||
parentSelector={() => document.body} |
|||
className={styles.modal} |
|||
> |
|||
<div onClick={closeChannelForm} className={styles.modalClose}> |
|||
<FaClose className={styles.close} /> |
|||
</div> |
|||
|
|||
<header className={styles.header}> |
|||
<h3>{channelFormHeader}</h3> |
|||
<div className={styles.progress} style={{ width: `${channelFormProgress}%` }} /> |
|||
</header> |
|||
|
|||
<div className={styles.content}> |
|||
{renderStep()} |
|||
</div> |
|||
|
|||
<Footer |
|||
step={channelform.step} |
|||
changeStep={changeStep} |
|||
stepTwoIsValid={stepTwoIsValid} |
|||
submit={() => openChannel({ pubkey: channelform.node_key, local_amt: channelform.local_amt, push_amt: channelform.push_amt })} |
|||
/> |
|||
</ReactModal> |
|||
) |
|||
} |
|||
|
|||
ChannelForm.propTypes = { |
|||
channelform: PropTypes.object.isRequired, |
|||
openChannel: PropTypes.func.isRequired, |
|||
closeChannelForm: PropTypes.func.isRequired, |
|||
changeStep: PropTypes.func.isRequired, |
|||
setNodeKey: PropTypes.func.isRequired, |
|||
setLocalAmount: PropTypes.func.isRequired, |
|||
setPushAmount: PropTypes.func.isRequired, |
|||
channelFormHeader: PropTypes.string.isRequired, |
|||
channelFormProgress: PropTypes.number.isRequired, |
|||
stepTwoIsValid: PropTypes.bool.isRequired, |
|||
peers: PropTypes.array.isRequired |
|||
} |
|||
|
|||
export default ChannelForm |
@ -0,0 +1,58 @@ |
|||
@import '../../variables.scss'; |
|||
|
|||
.modal { |
|||
position: relative; |
|||
width: 40%; |
|||
margin: 50px auto; |
|||
position: absolute; |
|||
top: auto; |
|||
left: 20%; |
|||
right: 0; |
|||
bottom: auto; |
|||
background: $white; |
|||
outline: none; |
|||
z-index: -2; |
|||
border: 1px solid $darkgrey; |
|||
} |
|||
|
|||
.modalClose { |
|||
position: absolute; |
|||
top: -13px; |
|||
right: -13px; |
|||
display: block; |
|||
font-size: 16px; |
|||
line-height: 27px; |
|||
width: 32px; |
|||
height: 32px; |
|||
background: $white; |
|||
border-radius: 50%; |
|||
color: $darkestgrey; |
|||
cursor: pointer; |
|||
text-align: center; |
|||
z-index: 2; |
|||
transition: all 0.25s; |
|||
} |
|||
|
|||
.modalClose:hover { |
|||
background: $darkgrey; |
|||
} |
|||
|
|||
.header { |
|||
padding: 20px; |
|||
background: $lightgrey; |
|||
text-align: center; |
|||
font-family: 'Jigsaw Light'; |
|||
text-transform: uppercase; |
|||
position: relative; |
|||
z-index: -2; |
|||
} |
|||
|
|||
.progress { |
|||
transition: all 0.2s ease; |
|||
background: $main; |
|||
position: absolute; |
|||
height: 100%; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: -1; |
|||
} |
@ -0,0 +1,44 @@ |
|||
import React from 'react' |
|||
import PropTypes from 'prop-types' |
|||
import styles from './Footer.scss' |
|||
|
|||
const Footer = ({ step, changeStep, stepTwoIsValid, submit }) => { |
|||
if (step === 1) { return null } |
|||
|
|||
// See if the next button on step 2 should be active
|
|||
const nextIsInactive = step === 2 && !stepTwoIsValid |
|||
|
|||
// Function that's called when the user clicks "next" in the form
|
|||
const nextFunc = () => { |
|||
if (nextIsInactive) { return } |
|||
|
|||
changeStep(step + 1) |
|||
} |
|||
|
|||
const rightButtonText = step === 4 ? 'Submit' : 'Next' |
|||
const rightButtonOnClick = step === 4 ? () => submit() : nextFunc |
|||
|
|||
return ( |
|||
<div className={styles.footer}> |
|||
<div className='buttonContainer'> |
|||
<div className='buttonPrimary' onClick={() => changeStep(step - 1)}> |
|||
Back |
|||
</div> |
|||
</div> |
|||
<div className='buttonContainer' onClick={rightButtonOnClick}> |
|||
<div className={`buttonPrimary ${nextIsInactive && 'inactive'}`}> |
|||
{rightButtonText} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
) |
|||
} |
|||
|
|||
Footer.propTypes = { |
|||
step: PropTypes.number.isRequired, |
|||
changeStep: PropTypes.func.isRequired, |
|||
stepTwoIsValid: PropTypes.bool.isRequired, |
|||
submit: PropTypes.func.isRequired |
|||
} |
|||
|
|||
export default Footer |
@ -0,0 +1,17 @@ |
|||
@import '../../variables.scss'; |
|||
|
|||
.footer { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
padding-bottom: 30px; |
|||
|
|||
div { |
|||
margin: 0 20px; |
|||
|
|||
div { |
|||
padding: 18px 60px 15px 60px; |
|||
color: $black; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,37 @@ |
|||
import React from 'react' |
|||
import PropTypes from 'prop-types' |
|||
import styles from './StepFour.scss' |
|||
|
|||
const StepFour = ({ node_key, local_amt, push_amt }) => ( |
|||
<div className={styles.container}> |
|||
<div className={styles.nodekey}> |
|||
<h4>Peer</h4> |
|||
<h2>{node_key}</h2> |
|||
</div> |
|||
|
|||
<div className={styles.amounts}> |
|||
<div className={styles.localamt}> |
|||
<h4>Local Amount</h4> |
|||
<h3>{local_amt}</h3> |
|||
</div> |
|||
<div className={styles.pushamt}> |
|||
<h4>Push Amount</h4> |
|||
<h3>{push_amt}</h3> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
) |
|||
|
|||
StepFour.propTypes = { |
|||
node_key: PropTypes.string.isRequired, |
|||
local_amt: PropTypes.oneOfType([ |
|||
PropTypes.number, |
|||
PropTypes.string |
|||
]), |
|||
push_amt: PropTypes.oneOfType([ |
|||
PropTypes.number, |
|||
PropTypes.string |
|||
]) |
|||
} |
|||
|
|||
export default StepFour |
@ -0,0 +1,36 @@ |
|||
@import '../../variables.scss'; |
|||
|
|||
.container { |
|||
padding: 50px; |
|||
|
|||
h4 { |
|||
text-transform: uppercase; |
|||
font-size: 14px; |
|||
margin-bottom: 10px; |
|||
} |
|||
|
|||
h3 { |
|||
text-align: center; |
|||
color: $main; |
|||
font-size: 50px; |
|||
} |
|||
} |
|||
|
|||
.nodekey { |
|||
margin-bottom: 50px; |
|||
padding: 20px; |
|||
border-bottom: 1px solid $main; |
|||
|
|||
h2 { |
|||
font-size: 12px; |
|||
font-weight: bold; |
|||
} |
|||
} |
|||
|
|||
.amounts { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: space-around; |
|||
margin-bottom: 50px; |
|||
padding: 20px; |
|||
} |
@ -0,0 +1,75 @@ |
|||
import React, { Component } from 'react' |
|||
import PropTypes from 'prop-types' |
|||
import { MdSearch } from 'react-icons/lib/md' |
|||
import styles from './StepOne.scss' |
|||
|
|||
class StepOne extends Component { |
|||
constructor(props) { |
|||
super(props); |
|||
this.state = { |
|||
peers: props.peers, |
|||
searchQuery: '' |
|||
} |
|||
|
|||
this.onSearchQuery = this.onSearchQuery.bind(this) |
|||
this.peerClicked = this.peerClicked.bind(this) |
|||
} |
|||
|
|||
onSearchQuery(searchQuery) { |
|||
const peers = this.props.peers.filter(peer => peer.pub_key.includes(searchQuery)) |
|||
|
|||
this.setState({ peers, searchQuery }) |
|||
} |
|||
|
|||
peerClicked(peer) { |
|||
const { setNodeKey, changeStep } = this.props |
|||
|
|||
setNodeKey(peer.pub_key) |
|||
changeStep(2) |
|||
} |
|||
|
|||
render() { |
|||
const { peers, searchQuery } = this.state |
|||
|
|||
return ( |
|||
<div> |
|||
<div className={styles.search}> |
|||
<label className={`${styles.label} ${styles.input}`} htmlFor='peersSearch'> |
|||
<MdSearch /> |
|||
</label> |
|||
<input |
|||
value={searchQuery} |
|||
onChange={event => this.onSearchQuery(event.target.value)} |
|||
className={`${styles.text} ${styles.input}`} |
|||
placeholder='Search your peers by their public key' |
|||
type='text' |
|||
id='peersSearch' |
|||
/> |
|||
</div> |
|||
|
|||
<ul className={styles.peers}> |
|||
{peers.length > 0 && |
|||
peers.map(peer => ( |
|||
<li |
|||
key={peer.peer_id} |
|||
className={styles.peer} |
|||
onClick={() => this.peerClicked(peer)} |
|||
> |
|||
<h4>{peer.address}</h4> |
|||
<h1>{peer.pub_key}</h1> |
|||
</li> |
|||
) |
|||
)} |
|||
</ul> |
|||
</div> |
|||
) |
|||
} |
|||
} |
|||
|
|||
StepOne.propTypes = { |
|||
peers: PropTypes.array.isRequired, |
|||
setNodeKey: PropTypes.func.isRequired, |
|||
changeStep: PropTypes.func.isRequired |
|||
} |
|||
|
|||
export default StepOne |
@ -0,0 +1,74 @@ |
|||
@import '../../variables.scss'; |
|||
|
|||
.peers { |
|||
h2 { |
|||
text-transform: uppercase; |
|||
font-weight: 200; |
|||
padding: 10px 0; |
|||
border-bottom: 1px solid $grey; |
|||
color: $darkestgrey; |
|||
} |
|||
} |
|||
|
|||
.search { |
|||
height: 50px; |
|||
padding: 2px; |
|||
border-bottom: 1px solid $darkgrey; |
|||
|
|||
.input { |
|||
display: inline-block; |
|||
vertical-align: top; |
|||
height: 100%; |
|||
} |
|||
|
|||
.label { |
|||
width: 5%; |
|||
line-height: 50px; |
|||
font-size: 16px; |
|||
text-align: center; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.text { |
|||
width: 95%; |
|||
outline: 0; |
|||
padding: 0; |
|||
border: 0; |
|||
border-radius: 0; |
|||
height: 50px; |
|||
font-size: 16px; |
|||
} |
|||
} |
|||
|
|||
.peer { |
|||
position: relative; |
|||
background: $white; |
|||
padding: 10px; |
|||
border-top: 1px solid $grey; |
|||
cursor: pointer; |
|||
transition: all 0.25s; |
|||
|
|||
&:hover { |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
&:first-child { |
|||
border: none; |
|||
} |
|||
|
|||
h4, h1 { |
|||
margin: 10px 0; |
|||
} |
|||
|
|||
h4 { |
|||
font-size: 12px; |
|||
font-weight: bold; |
|||
color: $black; |
|||
} |
|||
|
|||
h1 { |
|||
font-size: 14px; |
|||
font-weight: 200; |
|||
color: $main; |
|||
} |
|||
} |
@ -0,0 +1,50 @@ |
|||
import React, { Component } from 'react' |
|||
import PropTypes from 'prop-types' |
|||
import CurrencyIcon from 'components/CurrencyIcon' |
|||
import styles from './StepThree.scss' |
|||
|
|||
class StepThree extends Component { |
|||
render() { |
|||
const { push_amt, setPushAmount } = this.props |
|||
|
|||
return ( |
|||
<div className={styles.container}> |
|||
<div className={styles.explainer}> |
|||
<h2>Push Amount</h2> |
|||
<p> |
|||
The push amount is the amount of bitcoin (if any at all) you'd like |
|||
to "push" to the other side of the channel when it opens. |
|||
This amount will be set on the remote side of the channel as part of the initial commitment state. |
|||
</p> |
|||
</div> |
|||
|
|||
<form> |
|||
<label htmlFor='amount'> |
|||
<CurrencyIcon currency={'btc'} crypto={'btc'} /> |
|||
</label> |
|||
<input |
|||
type='number' |
|||
min='0' |
|||
max='0.16' |
|||
ref={(input) => { this.input = input }} |
|||
size='' |
|||
value={push_amt} |
|||
onChange={event => setPushAmount(event.target.value)} |
|||
id='amount' |
|||
style={{ width: isNaN((push_amt.length + 1) * 55) ? 140 : (push_amt.length + 1) * 55 }} |
|||
/> |
|||
</form> |
|||
</div> |
|||
) |
|||
} |
|||
} |
|||
|
|||
StepThree.propTypes = { |
|||
push_amt: PropTypes.oneOfType([ |
|||
PropTypes.number, |
|||
PropTypes.string |
|||
]).isRequired, |
|||
setPushAmount: PropTypes.func.isRequired |
|||
} |
|||
|
|||
export default StepThree |
@ -0,0 +1,58 @@ |
|||
@import '../../variables.scss'; |
|||
|
|||
.container { |
|||
margin-bottom: 50px; |
|||
padding: 20px; |
|||
|
|||
.explainer { |
|||
margin: 0px 0 50px 0; |
|||
padding-bottom: 20px; |
|||
border-bottom: 1px solid $lightgrey; |
|||
|
|||
h2 { |
|||
margin: 0 0 20px 0; |
|||
font-size: 28px; |
|||
} |
|||
|
|||
p { |
|||
line-height: 1.5; |
|||
font-size: 16px; |
|||
} |
|||
} |
|||
|
|||
form { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
label { |
|||
height: 200px; |
|||
color: $main; |
|||
|
|||
svg { |
|||
width: 65px; |
|||
height: 65px; |
|||
} |
|||
|
|||
svg[data-icon='ltc'] { |
|||
margin-right: 10px; |
|||
|
|||
g { |
|||
transform: scale(1.75) translate(-5px, -5px); |
|||
} |
|||
} |
|||
} |
|||
|
|||
input[type=number] { |
|||
color: $main; |
|||
width: 30px; |
|||
height: 200px; |
|||
font-size: 100px; |
|||
font-weight: 200; |
|||
border: none; |
|||
outline: 0; |
|||
-webkit-appearance: none; |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
import React, { Component } from 'react' |
|||
import PropTypes from 'prop-types' |
|||
import CurrencyIcon from 'components/CurrencyIcon' |
|||
import styles from './StepTwo.scss' |
|||
|
|||
class StepTwo extends Component { |
|||
render() { |
|||
const { local_amt, setLocalAmount } = this.props |
|||
|
|||
return ( |
|||
<div className={styles.container}> |
|||
<div className={styles.explainer}> |
|||
<h2>Local Amount</h2> |
|||
<p> |
|||
Local amount is the amount of bitcoin that you would like to commit to the channel. |
|||
This is the amount that will be sent in an on-chain transaction to open your Lightning channel. |
|||
</p> |
|||
</div> |
|||
|
|||
<form> |
|||
<label htmlFor='amount'> |
|||
<CurrencyIcon currency={'btc'} crypto={'btc'} /> |
|||
</label> |
|||
<input |
|||
type='number' |
|||
min='0' |
|||
max='0.16' |
|||
ref={(input) => { this.input = input }} |
|||
size='' |
|||
value={local_amt} |
|||
onChange={event => setLocalAmount(event.target.value)} |
|||
id='amount' |
|||
style={{ width: isNaN((local_amt.length + 1) * 55) ? 140 : (local_amt.length + 1) * 55 }} |
|||
/> |
|||
</form> |
|||
</div> |
|||
) |
|||
} |
|||
} |
|||
|
|||
StepTwo.propTypes = { |
|||
local_amt: PropTypes.oneOfType([ |
|||
PropTypes.string, |
|||
PropTypes.number |
|||
]).isRequired, |
|||
setLocalAmount: PropTypes.func.isRequired |
|||
} |
|||
|
|||
export default StepTwo |
@ -0,0 +1,58 @@ |
|||
@import '../../variables.scss'; |
|||
|
|||
.container { |
|||
margin-bottom: 50px; |
|||
padding: 20px; |
|||
|
|||
.explainer { |
|||
margin: 0px 0 50px 0; |
|||
padding-bottom: 20px; |
|||
border-bottom: 1px solid $lightgrey; |
|||
|
|||
h2 { |
|||
margin: 0 0 20px 0; |
|||
font-size: 28px; |
|||
} |
|||
|
|||
p { |
|||
line-height: 1.5; |
|||
font-size: 16px; |
|||
} |
|||
} |
|||
|
|||
form { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
label { |
|||
height: 200px; |
|||
color: $main; |
|||
|
|||
svg { |
|||
width: 65px; |
|||
height: 65px; |
|||
} |
|||
|
|||
svg[data-icon='ltc'] { |
|||
margin-right: 10px; |
|||
|
|||
g { |
|||
transform: scale(1.75) translate(-5px, -5px); |
|||
} |
|||
} |
|||
} |
|||
|
|||
input[type=number] { |
|||
color: $main; |
|||
width: 30px; |
|||
height: 200px; |
|||
font-size: 100px; |
|||
font-weight: 200; |
|||
border: none; |
|||
outline: 0; |
|||
-webkit-appearance: none; |
|||
} |
|||
} |
@ -0,0 +1,3 @@ |
|||
import ChannelForm from './ChannelForm' |
|||
|
|||
export default ChannelForm |
@ -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; |
|||
} |
|||
} |
|||
} |
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 156 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 954 B After Width: | Height: | Size: 954 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 263 B After Width: | Height: | Size: 263 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
@ -0,0 +1,130 @@ |
|||
import { createSelector } from 'reselect' |
|||
|
|||
// Initial State
|
|||
const initialState = { |
|||
isOpen: false, |
|||
node_key: '', |
|||
local_amt: 0, |
|||
push_amt: 0, |
|||
|
|||
step: 1 |
|||
} |
|||
|
|||
// Constants
|
|||
// ------------------------------------
|
|||
export const OPEN_CHANNEL_FORM = 'OPEN_CHANNEL_FORM' |
|||
export const CLOSE_CHANNEL_FORM = 'CLOSE_CHANNEL_FORM' |
|||
|
|||
export const SET_NODE_KEY = 'SET_NODE_KEY' |
|||
export const SET_LOCAL_AMOUNT = 'SET_LOCAL_AMOUNT' |
|||
export const SET_PUSH_AMOUNT = 'SET_PUSH_AMOUNT' |
|||
|
|||
export const CHANGE_STEP = 'CHANGE_STEP' |
|||
|
|||
export const RESET_CHANNEL_FORM = 'RESET_CHANNEL_FORM' |
|||
|
|||
// ------------------------------------
|
|||
// Actions
|
|||
// ------------------------------------
|
|||
export function openChannelForm() { |
|||
return { |
|||
type: OPEN_CHANNEL_FORM |
|||
} |
|||
} |
|||
|
|||
export function closeChannelForm() { |
|||
return { |
|||
type: CLOSE_CHANNEL_FORM |
|||
} |
|||
} |
|||
|
|||
export function setNodeKey(node_key) { |
|||
return { |
|||
type: SET_NODE_KEY, |
|||
node_key |
|||
} |
|||
} |
|||
|
|||
export function setLocalAmount(local_amt) { |
|||
return { |
|||
type: SET_LOCAL_AMOUNT, |
|||
local_amt |
|||
} |
|||
} |
|||
|
|||
export function setPushAmount(push_amt) { |
|||
return { |
|||
type: SET_PUSH_AMOUNT, |
|||
push_amt |
|||
} |
|||
} |
|||
|
|||
export function changeStep(step) { |
|||
return { |
|||
type: CHANGE_STEP, |
|||
step |
|||
} |
|||
} |
|||
|
|||
export function resetChannelForm() { |
|||
return { |
|||
type: RESET_CHANNEL_FORM |
|||
} |
|||
} |
|||
|
|||
// ------------------------------------
|
|||
// Action Handlers
|
|||
// ------------------------------------
|
|||
const ACTION_HANDLERS = { |
|||
[OPEN_CHANNEL_FORM]: state => ({ ...state, isOpen: true }), |
|||
[CLOSE_CHANNEL_FORM]: state => ({ ...state, isOpen: false }), |
|||
|
|||
[SET_NODE_KEY]: (state, { node_key }) => ({ ...state, node_key }), |
|||
[SET_LOCAL_AMOUNT]: (state, { local_amt }) => ({ ...state, local_amt }), |
|||
[SET_PUSH_AMOUNT]: (state, { push_amt }) => ({ ...state, push_amt }), |
|||
|
|||
[CHANGE_STEP]: (state, { step }) => ({ ...state, step }), |
|||
|
|||
[RESET_CHANNEL_FORM]: () => (initialState) |
|||
} |
|||
|
|||
const channelFormSelectors = {} |
|||
const channelFormStepSelector = state => state.channelform.step |
|||
const channelFormLocalAmountSelector = state => state.channelform.local_amt |
|||
|
|||
channelFormSelectors.channelFormHeader = createSelector( |
|||
channelFormStepSelector, |
|||
(step) => { |
|||
switch (step) { |
|||
case 1: |
|||
return 'Step 1: Select a peer' |
|||
case 2: |
|||
return 'Step 2: Set your local amount' |
|||
case 3: |
|||
return 'Step 3: Set your push amount' |
|||
default: |
|||
return 'Step 4: Create your channel' |
|||
} |
|||
} |
|||
) |
|||
|
|||
channelFormSelectors.channelFormProgress = createSelector( |
|||
channelFormStepSelector, |
|||
step => ((step - 1) / 3) * 100 |
|||
) |
|||
|
|||
channelFormSelectors.stepTwoIsValid = createSelector( |
|||
channelFormLocalAmountSelector, |
|||
local_amt => local_amt > 0 |
|||
) |
|||
|
|||
export { channelFormSelectors } |
|||
|
|||
// ------------------------------------
|
|||
// Reducer
|
|||
// ------------------------------------
|
|||
export default function channelFormReducer(state = initialState, action) { |
|||
const handler = ACTION_HANDLERS[action.type] |
|||
|
|||
return handler ? handler(state, action) : state |
|||
} |
@ -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 |
|||
} |
@ -0,0 +1,207 @@ |
|||
import React, { Component } from 'react' |
|||
import PropTypes from 'prop-types' |
|||
|
|||
import { FaAlignJustify, FaGlobe, FaAngleDown, FaRepeat } from 'react-icons/lib/fa' |
|||
import { MdSearch } from 'react-icons/lib/md' |
|||
|
|||
import OpenPendingChannel from 'components/Channels/OpenPendingChannel' |
|||
import ClosedPendingChannel from 'components/Channels/ClosedPendingChannel' |
|||
import Channel from 'components/Channels/Channel' |
|||
import NetworkChannels from 'components/Channels/NetworkChannels' |
|||
import ChannelForm from 'components/ChannelForm' |
|||
|
|||
import styles from './Channels.scss' |
|||
|
|||
class Channels extends Component { |
|||
componentWillMount() { |
|||
const { fetchChannels, fetchPeers, fetchDescribeNetwork } = this.props |
|||
|
|||
fetchChannels() |
|||
fetchPeers() |
|||
fetchDescribeNetwork() |
|||
} |
|||
|
|||
render() { |
|||
const { |
|||
fetchChannels, |
|||
closeChannel, |
|||
channels: { |
|||
searchQuery, |
|||
filterPulldown, |
|||
filter, |
|||
viewType |
|||
}, |
|||
|
|||
nonActiveFilters, |
|||
toggleFilterPulldown, |
|||
changeFilter, |
|||
|
|||
currentChannels, |
|||
openChannels, |
|||
updateChannelSearchQuery, |
|||
setViewType, |
|||
|
|||
openChannelForm, |
|||
|
|||
ticker, |
|||
currentTicker, |
|||
|
|||
channelFormProps, |
|||
|
|||
network, |
|||
identity_pubkey, |
|||
setCurrentChannel |
|||
} = this.props |
|||
|
|||
const refreshClicked = (event) => { |
|||
// store event in icon so we dont get an error when react clears it
|
|||
const icon = event.currentTarget |
|||
|
|||
// fetch peers
|
|||
fetchChannels() |
|||
|
|||
// 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.container} ${viewType === 1 && styles.graphview}`}> |
|||
<ChannelForm {...channelFormProps} /> |
|||
|
|||
<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 channels by funding transaction or remote public key' |
|||
type='text' |
|||
id='channelSearch' |
|||
/> |
|||
</div> |
|||
<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={() => setViewType(1)}> |
|||
<FaGlobe /> |
|||
</span> |
|||
</div> |
|||
<div className={styles.createChannelContainer}> |
|||
<div className={`buttonPrimary ${styles.newChannelButton}`} onClick={openChannelForm}> |
|||
Create new channel |
|||
</div> |
|||
</div> |
|||
</header> |
|||
|
|||
<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} hint--left`} data-hint='Refresh your peers list'> |
|||
<FaRepeat |
|||
style={{ verticalAlign: 'baseline' }} |
|||
onClick={refreshClicked} |
|||
/> |
|||
</section> |
|||
</div> |
|||
|
|||
<div className={`${styles.channels} ${filterPulldown && styles.fade}`}> |
|||
{ |
|||
viewType === 0 && |
|||
<ul className={viewType === 1 && styles.cardsContainer}> |
|||
{ |
|||
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 ( |
|||
<Channel |
|||
key={index} |
|||
ticker={ticker} |
|||
channel={channel} |
|||
closeChannel={closeChannel} |
|||
currentTicker={currentTicker} |
|||
/> |
|||
) |
|||
}) |
|||
} |
|||
</ul> |
|||
} |
|||
{ viewType === 1 && |
|||
<NetworkChannels |
|||
channels={openChannels} |
|||
network={network} |
|||
identity_pubkey={identity_pubkey} |
|||
setCurrentChannel={setCurrentChannel} |
|||
/> |
|||
} |
|||
</div> |
|||
</div> |
|||
) |
|||
} |
|||
} |
|||
|
|||
Channels.propTypes = { |
|||
fetchChannels: PropTypes.func.isRequired, |
|||
fetchPeers: PropTypes.func.isRequired, |
|||
|
|||
channels: PropTypes.object.isRequired, |
|||
currentChannels: PropTypes.array.isRequired, |
|||
openChannels: PropTypes.array.isRequired, |
|||
nonActiveFilters: PropTypes.array.isRequired, |
|||
|
|||
updateChannelSearchQuery: PropTypes.func.isRequired, |
|||
setViewType: PropTypes.func.isRequired, |
|||
setCurrentChannel: PropTypes.func.isRequired, |
|||
openChannelForm: PropTypes.func.isRequired, |
|||
closeChannel: PropTypes.func.isRequired, |
|||
toggleFilterPulldown: PropTypes.func.isRequired, |
|||
changeFilter: PropTypes.func.isRequired, |
|||
|
|||
ticker: PropTypes.object.isRequired, |
|||
currentTicker: PropTypes.object.isRequired, |
|||
|
|||
channelFormProps: PropTypes.object.isRequired, |
|||
|
|||
network: PropTypes.object.isRequired, |
|||
fetchDescribeNetwork: PropTypes.func.isRequired, |
|||
identity_pubkey: PropTypes.string.isRequired |
|||
} |
|||
|
|||
export default Channels |
@ -0,0 +1,148 @@ |
|||
@import '../../../variables.scss'; |
|||
|
|||
.container.graphview { |
|||
background: $black; |
|||
} |
|||
|
|||
.search { |
|||
height: 75px; |
|||
padding: 2px 25px; |
|||
border-bottom: 1px solid $darkgrey; |
|||
background: $white; |
|||
|
|||
.input { |
|||
display: inline-block; |
|||
vertical-align: top; |
|||
height: 100%; |
|||
} |
|||
|
|||
.label { |
|||
width: 5%; |
|||
line-height: 70px; |
|||
font-size: 25px; |
|||
text-align: center; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.text { |
|||
width: 95%; |
|||
outline: 0; |
|||
padding: 0; |
|||
border: 0; |
|||
border-radius: 0; |
|||
height: 68px; |
|||
font-size: 18px; |
|||
} |
|||
} |
|||
|
|||
.header { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.filtersContainer { |
|||
position: relative; |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
padding: 0 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 { |
|||
color: $bluegrey; |
|||
|
|||
&:hover { |
|||
cursor: pointer; |
|||
color: lighten($bluegrey, 10%); |
|||
} |
|||
} |
|||
} |
|||
|
|||
.layoutsContainer { |
|||
padding: 40px; |
|||
|
|||
span { |
|||
font-size: 30px; |
|||
color: $grey; |
|||
cursor: pointer; |
|||
transition: all 0.25s; |
|||
|
|||
&:nth-child(1) { |
|||
margin-right: 20px; |
|||
} |
|||
|
|||
&:hover { |
|||
color: $darkestgrey; |
|||
} |
|||
|
|||
&.active { |
|||
color: $darkestgrey; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.createChannelContainer { |
|||
padding: 40px; |
|||
|
|||
.newChannelButton { |
|||
font-size: 14px; |
|||
} |
|||
} |
|||
|
|||
.channels { |
|||
padding: 10px 40px 40px 40px; |
|||
transition: opacity 0.25s; |
|||
|
|||
&.fade { |
|||
opacity: 0.05; |
|||
} |
|||
|
|||
.cardsContainer { |
|||
display: flex; |
|||
justify-content: center; |
|||
flex-wrap: wrap; |
|||
box-sizing: border-box; |
|||
} |
|||
} |
@ -0,0 +1,103 @@ |
|||
import { withRouter } from 'react-router' |
|||
import { connect } from 'react-redux' |
|||
|
|||
import { |
|||
fetchChannels, |
|||
openChannel, |
|||
closeChannel, |
|||
updateChannelSearchQuery, |
|||
setViewType, |
|||
currentChannels, |
|||
|
|||
toggleFilterPulldown, |
|||
changeFilter, |
|||
|
|||
channelsSelectors |
|||
} from 'reducers/channels' |
|||
|
|||
import { |
|||
openChannelForm, |
|||
changeStep, |
|||
setNodeKey, |
|||
setLocalAmount, |
|||
setPushAmount, |
|||
closeChannelForm, |
|||
channelFormSelectors |
|||
} from 'reducers/channelform' |
|||
|
|||
import { fetchPeers } from 'reducers/peers' |
|||
|
|||
import { tickerSelectors } from 'reducers/ticker' |
|||
|
|||
import { fetchDescribeNetwork, setCurrentChannel } from '../../../reducers/network' |
|||
|
|||
import Channels from '../components/Channels' |
|||
|
|||
const mapDispatchToProps = { |
|||
fetchChannels, |
|||
openChannel, |
|||
closeChannel, |
|||
updateChannelSearchQuery, |
|||
setViewType, |
|||
toggleFilterPulldown, |
|||
changeFilter, |
|||
|
|||
openChannelForm, |
|||
closeChannelForm, |
|||
setNodeKey, |
|||
setLocalAmount, |
|||
setPushAmount, |
|||
changeStep, |
|||
|
|||
fetchPeers, |
|||
|
|||
fetchDescribeNetwork, |
|||
setCurrentChannel |
|||
} |
|||
|
|||
const mapStateToProps = state => ({ |
|||
channels: state.channels, |
|||
openChannels: state.channels.channels, |
|||
channelform: state.channelform, |
|||
peers: state.peers, |
|||
ticker: state.ticker, |
|||
network: state.network, |
|||
identity_pubkey: state.info.data.identity_pubkey, |
|||
|
|||
currentChannels: currentChannels(state), |
|||
activeChanIds: channelsSelectors.activeChanIds(state), |
|||
nonActiveFilters: channelsSelectors.nonActiveFilters(state), |
|||
|
|||
currentTicker: tickerSelectors.currentTicker(state), |
|||
|
|||
channelFormHeader: channelFormSelectors.channelFormHeader(state), |
|||
channelFormProgress: channelFormSelectors.channelFormProgress(state), |
|||
stepTwoIsValid: channelFormSelectors.stepTwoIsValid(state) |
|||
}) |
|||
|
|||
const mergeProps = (stateProps, dispatchProps, ownProps) => { |
|||
const channelFormProps = { |
|||
openChannel: dispatchProps.openChannel, |
|||
closeChannelForm: dispatchProps.closeChannelForm, |
|||
changeStep: dispatchProps.changeStep, |
|||
setNodeKey: dispatchProps.setNodeKey, |
|||
setLocalAmount: dispatchProps.setLocalAmount, |
|||
setPushAmount: dispatchProps.setPushAmount, |
|||
|
|||
channelform: stateProps.channelform, |
|||
channelFormHeader: stateProps.channelFormHeader, |
|||
channelFormProgress: stateProps.channelFormProgress, |
|||
stepTwoIsValid: stateProps.stepTwoIsValid, |
|||
peers: stateProps.peers.peers |
|||
} |
|||
|
|||
return { |
|||
...stateProps, |
|||
...dispatchProps, |
|||
...ownProps, |
|||
|
|||
channelFormProps |
|||
} |
|||
} |
|||
|
|||
export default withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(Channels)) |
@ -0,0 +1,3 @@ |
|||
import ChannelsContainer from './containers/ChannelsContainer' |
|||
|
|||
export default ChannelsContainer |
Before Width: | Height: | Size: 459 B |
Before Width: | Height: | Size: 1.3 KiB |
@ -1,5 +0,0 @@ |
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 50 50" version="1.1" width="100" height="100"> |
|||
<g id="surface1"> |
|||
<path style="" class="thunderstorm-path" d="M 26 5 C 21.890625 5 18.4375 7.535156 16.90625 11.09375 C 16.605469 11.0625 16.3125 11 16 11 C 11.792969 11 8.320313 13.925781 7.34375 17.84375 C 4.210938 19.253906 2 22.351563 2 26 C 2 30.957031 6.042969 35 11 35 L 16.125 35 L 15.0625 37.625 L 14.53125 39 L 20.5625 39 L 18.0625 45.65625 L 16.9375 48.625 L 19.5625 46.8125 L 32.5625 37.90625 L 33 37.59375 L 33 36 L 28.71875 36 L 29.28125 35 L 39 35 C 43.957031 35 48 30.957031 48 26 C 48 22.417969 45.851563 19.382813 42.8125 17.9375 C 42.292969 13.710938 38.910156 10.433594 34.625 10.125 C 32.90625 7.097656 29.726563 5 26 5 Z M 26 7 C 29.148438 7 31.847656 8.804688 33.15625 11.4375 L 33.4375 12 L 34.0625 12 C 37.792969 12.023438 40.777344 14.941406 40.96875 18.625 L 41 19.28125 L 41.59375 19.5 C 44.164063 20.535156 46 23.046875 46 26 C 46 29.878906 42.878906 33 39 33 L 30.375 33 L 32.875 28.5 L 33.6875 27 L 19.3125 27 L 19.0625 27.625 L 16.90625 33 L 11 33 C 7.121094 33 4 29.878906 4 26 C 4 23.007813 5.871094 20.476563 8.5 19.46875 L 9.03125 19.28125 L 9.125 18.71875 C 9.726563 15.464844 12.5625 13 16 13 C 16.433594 13 16.855469 13.046875 17.28125 13.125 L 18.15625 13.28125 L 18.40625 12.46875 C 19.46875 9.300781 22.460938 7 26 7 Z M 20.6875 29 L 30.28125 29 L 26.125 36.5 L 25.3125 38 L 28.90625 38 L 21.03125 43.40625 L 22.9375 38.34375 L 23.4375 37 L 17.5 37 Z "/> |
|||
</g> |
|||
</svg> |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.0 KiB |