@ -1,6 +1,7 @@ |
|||||
import React from 'react' |
import React from 'react' |
||||
import Isvg from 'react-inlinesvg' |
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 |
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 |