diff --git a/app/app.global.scss b/app/app.global.scss index 0a946033..1af35784 100644 --- a/app/app.global.scss +++ b/app/app.global.scss @@ -72,4 +72,94 @@ body { 100% { transform: rotate(360deg); } -} \ No newline at end of file +} + +// buttons + +.buttonPrimary, .buttonSecondary { + -webkit-user-select: none; + cursor: pointer; + display: block; + padding-left: 30px; + padding-right: 30px; + padding-top: 18px; + padding-bottom: 15px; + border-radius: 2px; + text-align: center; + font-size: 18px; + transition: none; + position: relative; + color: white; + + &:active { + transform: translate(0px, 3px); + outline: 0; + } +} + +.buttonPrimary { + background-color: $main; + box-shadow: 0 3px 0 0 darken($main, 10%); + + &:active { + box-shadow: inset 0 1px 1px 1px darken($main, 10%); + } +} + +.buttonPrimary.inactive { + opacity: 0.5; + cursor: auto; +} + +.buttonPrimary.inactive:active { + box-shadow: 0 3px 0 0 darken($main, 10%); + transform: none; +} + +.buttonSecondary { + background-color: $secondary; + box-shadow: 0 3px 0 0 darken($secondary, 10%); + + &:active { + box-shadow: inset 0 1px 1px 1px darken($secondary, 10%); + } +} + +.buttonContainer.circleContainer { + display: inline-block; + width: auto; + min-width: 0; + background: none; + border: none; + box-shadow: none; +} + +.buttonContainer .circle { + display: inline-block; + border-radius: 50px; +} + +.buttonContainer .circle.small { + width: 50px; + padding: 10px 0; +} + +.buttonContainer.small { + min-width: auto; + padding: 0; + border: none; + margin: 0 auto; + width: 80%; +} + +.buttonContainer .small.active { + box-shadow: inset 0 1px 1px 1px #1f4b2e; + transform: translate(0px, 3px); + outline: 0; + background: #002280; +} + +.buttonContainer.small .buttonPrimary { + padding: 10px 5px; + font-size: 15px; +} diff --git a/app/components/AnimatedCheckmark/AnimatedCheckmark.js b/app/components/AnimatedCheckmark/AnimatedCheckmark.js index 9e9240a3..f0be472a 100644 --- a/app/components/AnimatedCheckmark/AnimatedCheckmark.js +++ b/app/components/AnimatedCheckmark/AnimatedCheckmark.js @@ -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 diff --git a/app/components/ChannelForm/ChannelForm.js b/app/components/ChannelForm/ChannelForm.js new file mode 100644 index 00000000..b1a98f65 --- /dev/null +++ b/app/components/ChannelForm/ChannelForm.js @@ -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 diff --git a/app/components/ChannelForm/ChannelForm.scss b/app/components/ChannelForm/ChannelForm.scss new file mode 100644 index 00000000..d6a06f91 --- /dev/null +++ b/app/components/ChannelForm/ChannelForm.scss @@ -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; +} diff --git a/app/components/ChannelForm/Footer.js b/app/components/ChannelForm/Footer.js new file mode 100644 index 00000000..36675975 --- /dev/null +++ b/app/components/ChannelForm/Footer.js @@ -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 diff --git a/app/components/ChannelForm/Footer.scss b/app/components/ChannelForm/Footer.scss new file mode 100644 index 00000000..7d345b72 --- /dev/null +++ b/app/components/ChannelForm/Footer.scss @@ -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; + } + } +} \ No newline at end of file diff --git a/app/components/ChannelForm/StepFour.js b/app/components/ChannelForm/StepFour.js new file mode 100644 index 00000000..a1f64582 --- /dev/null +++ b/app/components/ChannelForm/StepFour.js @@ -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 diff --git a/app/components/ChannelForm/StepFour.scss b/app/components/ChannelForm/StepFour.scss new file mode 100644 index 00000000..8332f5a6 --- /dev/null +++ b/app/components/ChannelForm/StepFour.scss @@ -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; +} \ No newline at end of file diff --git a/app/components/ChannelForm/StepOne.js b/app/components/ChannelForm/StepOne.js new file mode 100644 index 00000000..0a92df0e --- /dev/null +++ b/app/components/ChannelForm/StepOne.js @@ -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 diff --git a/app/components/ChannelForm/StepOne.scss b/app/components/ChannelForm/StepOne.scss new file mode 100644 index 00000000..a9f25c73 --- /dev/null +++ b/app/components/ChannelForm/StepOne.scss @@ -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; + } +} \ No newline at end of file diff --git a/app/components/ChannelForm/StepThree.js b/app/components/ChannelForm/StepThree.js new file mode 100644 index 00000000..fef38172 --- /dev/null +++ b/app/components/ChannelForm/StepThree.js @@ -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 diff --git a/app/components/ChannelForm/StepThree.scss b/app/components/ChannelForm/StepThree.scss new file mode 100644 index 00000000..0a5e6370 --- /dev/null +++ b/app/components/ChannelForm/StepThree.scss @@ -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; + } +} \ No newline at end of file diff --git a/app/components/ChannelForm/StepTwo.js b/app/components/ChannelForm/StepTwo.js new file mode 100644 index 00000000..723653f4 --- /dev/null +++ b/app/components/ChannelForm/StepTwo.js @@ -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 diff --git a/app/components/ChannelForm/StepTwo.scss b/app/components/ChannelForm/StepTwo.scss new file mode 100644 index 00000000..0a5e6370 --- /dev/null +++ b/app/components/ChannelForm/StepTwo.scss @@ -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; + } +} \ No newline at end of file diff --git a/app/components/ChannelForm/index.js b/app/components/ChannelForm/index.js new file mode 100644 index 00000000..a30b36cb --- /dev/null +++ b/app/components/ChannelForm/index.js @@ -0,0 +1,3 @@ +import ChannelForm from './ChannelForm' + +export default ChannelForm diff --git a/app/components/Channels/Channel.js b/app/components/Channels/Channel.js index c74b2570..e3885b2b 100644 --- a/app/components/Channels/Channel.js +++ b/app/components/Channels/Channel.js @@ -1,56 +1,83 @@ import React from 'react' import PropTypes from 'prop-types' +import { FaCircle } from 'react-icons/lib/fa' import { btc } from 'utils' import styles from './Channel.scss' -const Channel = ({ ticker, channel, setChannel, currentTicker }) => ( - <li className={styles.channel} onClick={() => setChannel(channel)}> - <h1 className={styles.status}>Status: Open</h1> - <div className={styles.left}> - <section className={styles.remotePubkey}> - <span>Remote Pubkey</span> - <h4>{channel.remote_pubkey}</h4> - </section> - <section className={styles.channelPoint}> - <span>Channel Point</span> - <h4>{channel.channel_point}</h4> - </section> - </div> - <div className={styles.right}> - <section className={styles.capacity}> - <span>Capacity</span> - <h2> - { - ticker.currency === 'btc' ? - btc.satoshisToBtc(channel.capacity) - : - btc.satoshisToUsd(channel.capacity, currentTicker.price_usd) - } - </h2> - </section> - <div className={styles.balances}> - <section> - <h4> - { - ticker.currency === 'btc' ? - btc.satoshisToBtc(channel.local_balance) - : - btc.satoshisToUsd(channel.local_balance, currentTicker.price_usd) - } - </h4> - <span>Local</span> +const Channel = ({ ticker, channel, closeChannel, currentTicker }) => ( + <li className={styles.channel}> + <header className={styles.header}> + <div> + <span className={styles.status}>Open</span> + { + channel.active ? + <span className={styles.active}> + <FaCircle /> + <i>Active</i> + </span> + : + <span className={styles.notactive}> + <FaCircle /> + <i>Not Active</i> + </span> + } + </div> + <div> + <p + className={styles.close} + onClick={() => closeChannel({ channel_point: channel.channel_point })} + > + Close channel + </p> + </div> + </header> + <div className={styles.content}> + <div className={styles.left}> + <section className={styles.remotePubkey}> + <span>Remote Pubkey</span> + <h4>{channel.remote_pubkey}</h4> </section> - <section> - <h4> + <section className={styles.channelPoint}> + <span>Channel Point</span> + <h4>{channel.channel_point}</h4> + </section> + </div> + <div className={styles.right}> + <section className={styles.capacity}> + <span>Capacity</span> + <h2> { ticker.currency === 'btc' ? - btc.satoshisToBtc(channel.remote_balance) + btc.satoshisToBtc(channel.capacity) : - btc.satoshisToUsd(channel.remote_balance, currentTicker.price_usd) + btc.satoshisToUsd(channel.capacity, currentTicker.price_usd) } - </h4> - <span>Remote</span> + </h2> </section> + <div className={styles.balances}> + <section> + <span>Local</span> + <h4> + { + ticker.currency === 'btc' ? + btc.satoshisToBtc(channel.local_balance) + : + btc.satoshisToUsd(channel.local_balance, currentTicker.price_usd) + } + </h4> + </section> + <section> + <span>Remote</span> + <h4> + { + ticker.currency === 'btc' ? + btc.satoshisToBtc(channel.remote_balance) + : + btc.satoshisToUsd(channel.remote_balance, currentTicker.price_usd) + } + </h4> + </section> + </div> </div> </div> </li> @@ -59,7 +86,7 @@ const Channel = ({ ticker, channel, setChannel, currentTicker }) => ( Channel.propTypes = { ticker: PropTypes.object.isRequired, channel: PropTypes.object.isRequired, - setChannel: PropTypes.func.isRequired, + closeChannel: PropTypes.func.isRequired, currentTicker: PropTypes.object.isRequired } diff --git a/app/components/Channels/Channel.scss b/app/components/Channels/Channel.scss index b14fcd06..e7eefe52 100644 --- a/app/components/Channels/Channel.scss +++ b/app/components/Channels/Channel.scss @@ -2,12 +2,10 @@ .channel { position: relative; - background: $white; + background: $lightgrey; + margin: 5px 0; padding: 10px; - display: flex; - flex-direction: row; - justify-content: space-between; - border-top: 1px solid $grey; + border-top: 1px solid $white; cursor: pointer; transition: all 0.25s; @@ -19,21 +17,58 @@ border: none; } - .status { - color: $main; - position: absolute; - top: 0; - left: 10px; - padding: 10px; - text-transform: uppercase; - font-weight: bold; - font-size: 10px; + .header { + display: flex; + flex-direction: row; + justify-content: space-between; + + .status, .active, .notactive { + padding: 10px; + text-transform: uppercase; + font-weight: bold; + font-size: 10px; + } + + .status { + color: $main; + } + + .active i, .notactive i { + margin-left: 5px; + } + + .active { + color: $green; + } + + .notactive { + color: $red; + } + + .close { + padding: 10px; + font-size: 10px; + text-transform: uppercase; + color: $red; + cursor: pointer; + + &:hover { + color: lighten($red, 10%); + text-decoration: underline; + } + } } + .content { + display: flex; + flex-direction: row; + justify-content: space-between; + } + + .left, .right { padding: 0 10px; margin-bottom: 5; - margin-top: 25px; section { margin-bottom: 20px; @@ -60,7 +95,6 @@ .left { flex: 7; - border-right: 1px solid $grey; } .right { @@ -68,7 +102,6 @@ .capacity { text-align: center; - border-bottom: 1px solid $grey; margin-bottom: 10px; } @@ -84,10 +117,6 @@ color: $main; font-size: 16px; } - - &:first-child { - border-right: 1px solid $grey; - } } } } diff --git a/app/components/Channels/ClosedPendingChannel.js b/app/components/Channels/ClosedPendingChannel.js index b9f1e3d8..9e7eaf94 100644 --- a/app/components/Channels/ClosedPendingChannel.js +++ b/app/components/Channels/ClosedPendingChannel.js @@ -6,7 +6,7 @@ import styles from './ClosedPendingChannel.scss' const ClosedPendingChannel = ({ ticker, channel: { channel, closing_txid }, currentTicker, explorerLinkBase }) => ( <li className={styles.channel} onClick={() => shell.openExternal(`${explorerLinkBase}/tx/${closing_txid}`)}> - <h1 className={styles.closing}>Status: Closing</h1> + <h1 className={styles.closing}>Closing Channel...</h1> <div className={styles.left}> <section className={styles.remotePubkey}> <span>Remote Pubkey</span> diff --git a/app/components/Channels/NetworkChannels.js b/app/components/Channels/NetworkChannels.js new file mode 100644 index 00000000..5689fa9d --- /dev/null +++ b/app/components/Channels/NetworkChannels.js @@ -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 diff --git a/app/components/Channels/NetworkChannels.scss b/app/components/Channels/NetworkChannels.scss new file mode 100644 index 00000000..6febd4db --- /dev/null +++ b/app/components/Channels/NetworkChannels.scss @@ -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; + } + } +} \ No newline at end of file diff --git a/app/components/Channels/OpenPendingChannel.js b/app/components/Channels/OpenPendingChannel.js index f83054b3..ea46fad2 100644 --- a/app/components/Channels/OpenPendingChannel.js +++ b/app/components/Channels/OpenPendingChannel.js @@ -6,7 +6,7 @@ import styles from './OpenPendingChannel.scss' const OpenPendingChannel = ({ ticker, channel: { channel }, currentTicker, explorerLinkBase }) => ( <li className={styles.channel} onClick={() => shell.openExternal(`${explorerLinkBase}/tx/${channel.channel_point.split(':')[0]}`)}> - <h1 className={styles.pending}>Status: Pending</h1> + <h1 className={styles.pending}>Opening Channel...</h1> <div className={styles.left}> <section className={styles.remotePubkey}> <span>Remote Pubkey</span> diff --git a/app/components/Channels/OpenPendingChannel.scss b/app/components/Channels/OpenPendingChannel.scss index 2308a785..a191915e 100644 --- a/app/components/Channels/OpenPendingChannel.scss +++ b/app/components/Channels/OpenPendingChannel.scss @@ -2,7 +2,7 @@ .channel { position: relative; - background: $white; + background: $lightgrey; padding: 10px; display: flex; flex-direction: row; @@ -13,7 +13,7 @@ opacity: 0.5; .pending { - color: $green; + color: $main; position: absolute; top: 0; left: 10px; diff --git a/app/components/CryptoIcon/CryptoIcon.js b/app/components/CryptoIcon/CryptoIcon.js index 4bb3e83f..c9182a01 100644 --- a/app/components/CryptoIcon/CryptoIcon.js +++ b/app/components/CryptoIcon/CryptoIcon.js @@ -1,15 +1,16 @@ import React from 'react' import PropTypes from 'prop-types' -import path from 'path' -import { FaBitcoin } from 'react-icons/lib/fa' import Isvg from 'react-inlinesvg' +import skinnyBitcoinIcon from 'icons/skinny_bitcoin.svg' +import litecoinIcon from 'icons/litecoin.svg' + const CryptoIcon = ({ currency, styles }) => { switch (currency) { case 'btc': - return <FaBitcoin style={styles} /> + return <Isvg style={styles} src={skinnyBitcoinIcon} /> case 'ltc': - return <Isvg style={styles} src={path.join(__dirname, '..', 'resources/litecoin.svg')} /> + return <Isvg style={styles} src={litecoinIcon} /> default: return <span /> } diff --git a/app/components/Form/PayForm.js b/app/components/Form/PayForm.js index 5f84cc71..0ba33c13 100644 --- a/app/components/Form/PayForm.js +++ b/app/components/Form/PayForm.js @@ -13,6 +13,7 @@ class PayForm extends Component { // If on-chain, focus on amount to let user know it's editable if (isOnchain) { this.amountInput.focus() } + // If LN go retrieve invoice details if ((prevProps.payform.payInput !== payInput) && isLn) { fetchInvoice(payInput) diff --git a/app/components/Form/PayForm.scss b/app/components/Form/PayForm.scss index 572434c8..bc4163ce 100644 --- a/app/components/Form/PayForm.scss +++ b/app/components/Form/PayForm.scss @@ -62,12 +62,13 @@ } } - input[type=number],, input[type=text] { + input[type=number], input[type=text] { width: 100px; font-size: 180px; border: none; outline: 0; -webkit-appearance: none; + font-weight: 200; } } diff --git a/app/components/Form/RequestForm.scss b/app/components/Form/RequestForm.scss index 373ac924..06899c57 100644 --- a/app/components/Form/RequestForm.scss +++ b/app/components/Form/RequestForm.scss @@ -44,6 +44,7 @@ border: none; outline: 0; -webkit-appearance: none; + font-weight: 200; } } diff --git a/app/components/LoadingBolt/LoadingBolt.js b/app/components/LoadingBolt/LoadingBolt.js index 13351372..98a0b053 100644 --- a/app/components/LoadingBolt/LoadingBolt.js +++ b/app/components/LoadingBolt/LoadingBolt.js @@ -1,12 +1,14 @@ import React from 'react' -import path from 'path' import Isvg from 'react-inlinesvg' + +import cloudboltIcon from 'icons/cloudbolt.svg' + import styles from './LoadingBolt.scss' const LoadingBolt = () => ( <div className={styles.container}> <div className={styles.content}> - <Isvg className={styles.bolt} src={path.join(__dirname, '..', 'resources/cloudbolt.svg')} /> + <Isvg className={styles.bolt} src={cloudboltIcon} /> <h1>loading</h1> </div> </div> diff --git a/app/components/Nav/Nav.js b/app/components/Nav/Nav.js index 3fe72ff0..47871a6e 100644 --- a/app/components/Nav/Nav.js +++ b/app/components/Nav/Nav.js @@ -1,8 +1,13 @@ -import path from 'path' import React from 'react' import PropTypes from 'prop-types' import { NavLink } from 'react-router-dom' import Isvg from 'react-inlinesvg' + +import walletIcon from 'icons/wallet.svg' +import peersIcon from 'icons/peers.svg' +import channelsIcon from 'icons/channels.svg' +import settingsIcon from 'icons/settings.svg' + import styles from './Nav.scss' const Nav = ({ openPayForm, openRequestForm }) => ( @@ -15,37 +20,37 @@ const Nav = ({ openPayForm, openRequestForm }) => ( <NavLink exact to='/' activeClassName={styles.active} className={styles.link}> <span className={styles.activeBorder} /> <li> - <Isvg styles={{ verticalAlign: 'middle' }} src={path.join(__dirname, '..', 'resources/icons/wallet.svg')} /> + <Isvg styles={{ verticalAlign: 'middle' }} src={walletIcon} /> <span>Wallet</span> </li> </NavLink> <NavLink exact to='/wallet' activeClassName={styles.active} className={styles.link}> <span className={styles.activeBorder} /> <li> - <Isvg styles={{ verticalAlign: 'middle' }} src={path.join(__dirname, '..', 'resources/icons/peers.svg')} /> + <Isvg styles={{ verticalAlign: 'middle' }} src={peersIcon} /> <span>Peers</span> </li> </NavLink> <NavLink exact to='/channels' activeClassName={styles.active} className={styles.link}> <span className={styles.activeBorder} /> <li> - <Isvg styles={{ verticalAlign: 'middle' }} src={path.join(__dirname, '..', 'resources/icons/channels.svg')} /> + <Isvg styles={{ verticalAlign: 'middle' }} src={channelsIcon} /> <span>Channels</span> </li> </NavLink> <NavLink exact to='/settings' activeClassName={styles.active} className={styles.link}> <span className={styles.activeBorder} /> <li> - <Isvg styles={{ verticalAlign: 'middle' }} src={path.join(__dirname, '..', 'resources/icons/settings.svg')} /> + <Isvg styles={{ verticalAlign: 'middle' }} src={settingsIcon} /> <span>Settings</span> </li> </NavLink> </ul> <div className={styles.buttons}> - <div className={styles.button} onClick={openPayForm}> + <div className={`buttonPrimary ${styles.button}`} onClick={openPayForm}> <span>Pay</span> </div> - <div className={styles.button} onClick={openRequestForm}> + <div className={`buttonPrimary ${styles.button}`} onClick={openRequestForm}> <span>Request</span> </div> </div> diff --git a/app/components/Nav/Nav.scss b/app/components/Nav/Nav.scss index ea9aee2c..1d20ed8d 100644 --- a/app/components/Nav/Nav.scss +++ b/app/components/Nav/Nav.scss @@ -185,18 +185,11 @@ } .button { - text-align: center; - background: $main; margin-bottom: 20px; - padding: 20px 10px; font-weight: bold; cursor: pointer; text-transform: uppercase; letter-spacing: .2px; - - &:hover { - background: lighten($main, 5%); - } } .content { diff --git a/app/components/Wallet/Wallet.js b/app/components/Wallet/Wallet.js index dfcd06be..19019995 100644 --- a/app/components/Wallet/Wallet.js +++ b/app/components/Wallet/Wallet.js @@ -1,10 +1,11 @@ -import path from 'path' import React, { Component } from 'react' import PropTypes from 'prop-types' import { FaQrcode } from 'react-icons/lib/fa' import Isvg from 'react-inlinesvg' import { btc } from 'utils' +import skinnyBitcoinIcon from 'icons/skinny_bitcoin.svg' import ReceiveModal from './ReceiveModal' + import styles from './Wallet.scss' class Wallet extends Component { @@ -39,7 +40,7 @@ class Wallet extends Component { <div className={styles.content}> <div className={styles.left}> <div className={styles.leftContent}> - <Isvg className={styles.bitcoinLogo} src={path.join(__dirname, '..', 'resources/icons/skinny_bitcoin.svg')} /> + <Isvg className={styles.bitcoinLogo} src={skinnyBitcoinIcon} /> <div className={styles.details}> <h1>{btc.satoshisToBtc(parseFloat(balance.walletBalance) + parseFloat(balance.channelBalance))} BTC</h1> <span>{btc.satoshisToBtc(balance.walletBalance)} available</span> diff --git a/app/components/Wallet/Wallet.scss b/app/components/Wallet/Wallet.scss index be5d1915..88b4e6bf 100644 --- a/app/components/Wallet/Wallet.scss +++ b/app/components/Wallet/Wallet.scss @@ -52,9 +52,9 @@ .rightContent { display: flex; - flex-direction: column; - justify-content: center; - align-items: center; + flex-direction: row; + justify-content: flex-end; + align-items: right; height: calc(100% - 50px); div { diff --git a/resources/icons/1024x1024.png b/app/icons/1024x1024.png similarity index 100% rename from resources/icons/1024x1024.png rename to app/icons/1024x1024.png diff --git a/resources/icons/128x128.png b/app/icons/128x128.png similarity index 100% rename from resources/icons/128x128.png rename to app/icons/128x128.png diff --git a/resources/icons/16x16.png b/app/icons/16x16.png similarity index 100% rename from resources/icons/16x16.png rename to app/icons/16x16.png diff --git a/resources/icons/24x24.png b/app/icons/24x24.png similarity index 100% rename from resources/icons/24x24.png rename to app/icons/24x24.png diff --git a/resources/icons/256x256.png b/app/icons/256x256.png similarity index 100% rename from resources/icons/256x256.png rename to app/icons/256x256.png diff --git a/resources/icons/32x32.png b/app/icons/32x32.png similarity index 100% rename from resources/icons/32x32.png rename to app/icons/32x32.png diff --git a/resources/icons/48x48.png b/app/icons/48x48.png similarity index 100% rename from resources/icons/48x48.png rename to app/icons/48x48.png diff --git a/resources/icons/512x512.png b/app/icons/512x512.png similarity index 100% rename from resources/icons/512x512.png rename to app/icons/512x512.png diff --git a/resources/icons/64x64.png b/app/icons/64x64.png similarity index 100% rename from resources/icons/64x64.png rename to app/icons/64x64.png diff --git a/resources/icons/96x96.png b/app/icons/96x96.png similarity index 100% rename from resources/icons/96x96.png rename to app/icons/96x96.png diff --git a/resources/icons/channels.svg b/app/icons/channels.svg similarity index 100% rename from resources/icons/channels.svg rename to app/icons/channels.svg diff --git a/app/icons/cloudbolt.svg b/app/icons/cloudbolt.svg new file mode 100644 index 00000000..845b27a2 --- /dev/null +++ b/app/icons/cloudbolt.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="79.536px" height="79.536px" viewBox="0 0 79.536 79.536" style="enable-background:new 0 0 79.536 79.536;" + xml:space="preserve"> +<g> + <g> + <path class="load-draw-svg" style="fill:#010002;" d="M78.967,29.396c0,8.124-6.582,14.7-14.706,14.7c-0.58,0-6.131,0-13.489,0 + c-4.464,0-9.574,0-14.685,0c-12.896,0-25.626,0-26.942,0c-4.735,0-8.575-1.799-8.575-6.54c0-4.735,3.84-8.575,8.575-8.575 + c0.857,0,1.675,0.171,2.47,0.409c0-0.067-0.021-0.132-0.021-0.202c0-5.525,4.479-9.999,10.004-9.999 + c0.228,0,0.456,0.052,0.687,0.067c-0.013-0.233-0.075-0.451-0.075-0.684C22.209,8.313,30.522,0,40.788,0 + c9.264,0,16.896,6.814,18.289,15.689c1.61-0.616,3.351-0.991,5.184-0.991C72.385,14.698,78.967,21.279,78.967,29.396z + M49.177,47.618H34.504c-4.306,4.329-11.283,11.34-11.363,11.34h11.757L23.146,79.536l26.45-23.52h-8.818L49.177,47.618z"/> + </g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +</svg> \ No newline at end of file diff --git a/resources/litecoin.svg b/app/icons/litecoin.svg similarity index 100% rename from resources/litecoin.svg rename to app/icons/litecoin.svg diff --git a/resources/icons/peers.svg b/app/icons/peers.svg similarity index 100% rename from resources/icons/peers.svg rename to app/icons/peers.svg diff --git a/resources/icons/settings.svg b/app/icons/settings.svg similarity index 100% rename from resources/icons/settings.svg rename to app/icons/settings.svg diff --git a/resources/icons/skinny_bitcoin.svg b/app/icons/skinny_bitcoin.svg similarity index 100% rename from resources/icons/skinny_bitcoin.svg rename to app/icons/skinny_bitcoin.svg diff --git a/resources/icons/wallet.svg b/app/icons/wallet.svg similarity index 100% rename from resources/icons/wallet.svg rename to app/icons/wallet.svg diff --git a/app/lnd/methods/index.js b/app/lnd/methods/index.js index 98e82106..ca2603e2 100644 --- a/app/lnd/methods/index.js +++ b/app/lnd/methods/index.js @@ -33,6 +33,17 @@ export default function (lnd, event, msg, data) { }) .catch(error => console.log('info error: ', error)) break + case 'describeNetwork': + networkController.describeGraph(lnd) + .then(networkData => event.sender.send('receiveDescribeNetwork', networkData)) + .catch(error => console.log('describeGraph error: ', error)) + break + case 'queryRoutes': + // Data looks like { pubkey: String, amount: Number } + networkController.queryRoutes(lnd, data) + .then(routes => event.sender.send('receiveQueryRoutes', routes)) + .catch(error => console.log('queryRoutes error: ', error)) + break case 'newaddress': // Data looks like { address: '' } walletController.newAddress(lnd, data.type) diff --git a/app/reducers/channelform.js b/app/reducers/channelform.js new file mode 100644 index 00000000..50a92848 --- /dev/null +++ b/app/reducers/channelform.js @@ -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 +} diff --git a/app/reducers/channels.js b/app/reducers/channels.js index 74b6cf78..d6225ed7 100644 --- a/app/reducers/channels.js +++ b/app/reducers/channels.js @@ -1,5 +1,7 @@ import { createSelector } from 'reselect' import { ipcRenderer } from 'electron' +import { btc } from 'utils' +import { closeChannelForm } from './channelform' import { setError } from './error' // ------------------------------------ // Constants @@ -19,6 +21,13 @@ export const CLOSING_CHANNEL = 'CLOSING_CHANNEL' export const CLOSING_SUCCESSFUL = 'CLOSING_SUCCESSFUL' export const CLOSING_FAILURE = 'CLOSING_FAILURE' +export const UPDATE_SEARCH_QUERY = 'UPDATE_SEARCH_QUERY' + +export const SET_VIEW_TYPE = 'SET_VIEW_TYPE' + +export const TOGGLE_PULLDOWN = 'TOGGLE_PULLDOWN' +export const CHANGE_FILTER = 'CHANGE_FILTER' + // ------------------------------------ // Actions // ------------------------------------ @@ -67,6 +76,20 @@ export function openingFailure() { } } +export function updateChannelSearchQuery(searchQuery) { + return { + type: UPDATE_SEARCH_QUERY, + searchQuery + } +} + +export function setViewType(viewType) { + return { + type: SET_VIEW_TYPE, + viewType + } +} + // Send IPC event for peers export const fetchChannels = () => async (dispatch) => { dispatch(getChannels()) @@ -77,7 +100,10 @@ export const fetchChannels = () => async (dispatch) => { export const receiveChannels = (event, { channels, pendingChannels }) => dispatch => dispatch({ type: RECEIVE_CHANNELS, channels, pendingChannels }) // Send IPC event for opening a channel -export const openChannel = ({ pubkey, localamt, pushamt }) => (dispatch) => { +export const openChannel = ({ pubkey, local_amt, push_amt }) => (dispatch) => { + const localamt = btc.btcToSatoshis(local_amt) + const pushamt = btc.btcToSatoshis(push_amt) + dispatch(openingChannel()) ipcRenderer.send('lnd', { msg: 'openChannel', data: { pubkey, localamt, pushamt } }) } @@ -86,6 +112,7 @@ export const openChannel = ({ pubkey, localamt, pushamt }) => (dispatch) => { // Receive IPC event for openChannel export const channelSuccessful = () => (dispatch) => { dispatch(fetchChannels()) + dispatch(closeChannelForm()) } // Receive IPC event for updated channel @@ -94,8 +121,7 @@ export const pushchannelupdated = () => (dispatch) => { } // Receive IPC event for channel end -export const pushchannelend = (event, channelEndData) => (dispatch) => { - console.log('channelEndData: ', channelEndData) +export const pushchannelend = event => (dispatch) => { // eslint-disable-line dispatch(fetchChannels()) } @@ -106,8 +132,7 @@ export const pushchannelerror = (event, { error }) => (dispatch) => { } // Receive IPC event for channel status -export const pushchannelstatus = (event, channelStatusData) => (dispatch) => { - console.log('channel Status data: ', channelStatusData) +export const pushchannelstatus = event => (dispatch) => { // eslint-disable-line dispatch(fetchChannels()) } @@ -156,6 +181,19 @@ export const pushclosechannelstatus = () => (dispatch) => { dispatch(fetchChannels()) } +export function toggleFilterPulldown() { + return { + type: TOGGLE_PULLDOWN + } +} + +export function changeFilter(filter) { + return { + type: CHANGE_FILTER, + filter + } +} + // ------------------------------------ // Action Handlers // ------------------------------------ @@ -174,7 +212,14 @@ const ACTION_HANDLERS = { [OPENING_CHANNEL]: state => ({ ...state, openingChannel: true }), [OPENING_FAILURE]: state => ({ ...state, openingChannel: false }), - [CLOSING_CHANNEL]: state => ({ ...state, closingChannel: true }) + [CLOSING_CHANNEL]: state => ({ ...state, closingChannel: true }), + + [UPDATE_SEARCH_QUERY]: (state, { searchQuery }) => ({ ...state, searchQuery }), + + [SET_VIEW_TYPE]: (state, { viewType }) => ({ ...state, viewType }), + + [TOGGLE_PULLDOWN]: state => ({ ...state, filterPulldown: !state.filterPulldown }), + [CHANGE_FILTER]: (state, { filter }) => ({ ...state, filterPulldown: false, filter }) } const channelsSelectors = {} @@ -183,20 +228,88 @@ const channelsSelector = state => state.channels.channels const pendingOpenChannelsSelector = state => state.channels.pendingChannels.pending_open_channels const pendingClosedChannelsSelector = state => state.channels.pendingChannels.pending_closing_channels const pendingForceClosedChannelsSelector = state => state.channels.pendingChannels.pending_force_closing_channels +const channelSearchQuerySelector = state => state.channels.searchQuery +const filtersSelector = state => state.channels.filters +const filterSelector = state => state.channels.filter channelsSelectors.channelModalOpen = createSelector( channelSelector, channel => (!!channel) ) -channelsSelectors.allChannels = createSelector( +const activeChannels = createSelector( + channelsSelector, + openChannels => openChannels.filter(channel => channel.active) +) + +const closingPendingChannels = createSelector( + pendingClosedChannelsSelector, + pendingForceClosedChannelsSelector, + (pendingClosedChannels, pendingForcedClosedChannels) => [...pendingClosedChannels, ...pendingForcedClosedChannels] +) + +const allChannels = createSelector( channelsSelector, pendingOpenChannelsSelector, pendingClosedChannelsSelector, pendingForceClosedChannelsSelector, - (channels, pendingOpenChannels, pendingClosedChannels, pendingForcedClosedChannels) => ( - [...channels, ...pendingOpenChannels, ...pendingClosedChannels, ...pendingForcedClosedChannels] - ) + channelSearchQuerySelector, + (channels, pendingOpenChannels, pendingClosedChannels, pendingForcedClosedChannels, searchQuery) => { + const filteredChannels = channels.filter(channel => channel.remote_pubkey.includes(searchQuery) || channel.channel_point.includes(searchQuery)) // eslint-disable-line + const filteredPendingOpenChannels = pendingOpenChannels.filter(channel => channel.channel.remote_node_pub.includes(searchQuery) || channel.channel.channel_point.includes(searchQuery)) // eslint-disable-line + const filteredPendingClosedChannels = pendingClosedChannels.filter(channel => channel.channel.remote_node_pub.includes(searchQuery) || channel.channel.channel_point.includes(searchQuery)) // eslint-disable-line + const filteredPendingForcedClosedChannels = pendingForcedClosedChannels.filter(channel => channel.channel.remote_node_pub.includes(searchQuery) || channel.channel.channel_point.includes(searchQuery)) // eslint-disable-line + + + return [...filteredChannels, ...filteredPendingOpenChannels, ...filteredPendingClosedChannels, ...filteredPendingForcedClosedChannels] + } +) + +channelsSelectors.activeChanIds = createSelector( + channelsSelector, + channels => channels.map(channel => channel.chan_id) +) + +channelsSelectors.nonActiveFilters = createSelector( + filtersSelector, + filterSelector, + (filters, filter) => filters.filter(f => f.key !== filter.key) +) + +export const currentChannels = createSelector( + allChannels, + activeChannels, + channelsSelector, + pendingOpenChannelsSelector, + closingPendingChannels, + filterSelector, + channelSearchQuerySelector, + (allChannelsArr, activeChannelsArr, openChannels, pendingOpenChannels, pendingClosedChannels, filter, searchQuery) => { + // Helper function to deliver correct channel array based on filter + const filteredArray = (filterKey) => { + switch (filterKey) { + case 'ALL_CHANNELS': + return allChannelsArr + case 'ACTIVE_CHANNELS': + return activeChannelsArr + case 'OPEN_CHANNELS': + return openChannels + case 'OPEN_PENDING_CHANNELS': + return pendingOpenChannels + case 'CLOSING_PENDING_CHANNELS': + return pendingClosedChannels + default: + return [] + } + } + + const channelArray = filteredArray(filter.key) + + return channelArray.filter(channel => (Object.prototype.hasOwnProperty.call(channel, 'channel') ? + channel.channel.remote_node_pub.includes(searchQuery) || channel.channel.channel_point.includes(searchQuery) + : + channel.remote_pubkey.includes(searchQuery) || channel.channel_point.includes(searchQuery))) + } ) export { channelsSelectors } @@ -221,7 +334,19 @@ const initialState = { push_amt: '' }, openingChannel: false, - closingChannel: false + closingChannel: false, + searchQuery: '', + viewType: 0, + + filterPulldown: false, + filter: { key: 'ALL_CHANNELS', name: 'All Channels' }, + filters: [ + { key: 'ALL_CHANNELS', name: 'All Channels' }, + { key: 'ACTIVE_CHANNELS', name: 'Active Channels' }, + { key: 'OPEN_CHANNELS', name: 'Open Channels' }, + { key: 'OPEN_PENDING_CHANNELS', name: 'Open Pending Channels' }, + { key: 'CLOSING_PENDING_CHANNELS', name: 'Closing Pending Channels' } + ] } export default function channelsReducer(state = initialState, action) { diff --git a/app/reducers/index.js b/app/reducers/index.js index 20e0893b..6e77afba 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -8,6 +8,7 @@ import balance from './balance' import payment from './payment' import peers from './peers' import channels from './channels' +import channelform from './channelform' import form from './form' import payform from './payform' @@ -18,6 +19,7 @@ import modal from './modal' import address from './address' import transaction from './transaction' import activity from './activity' +import network from './network' import error from './error' const rootReducer = combineReducers({ @@ -29,6 +31,7 @@ const rootReducer = combineReducers({ payment, peers, channels, + channelform, form, payform, @@ -39,6 +42,7 @@ const rootReducer = combineReducers({ address, transaction, activity, + network, error }) diff --git a/app/reducers/ipc.js b/app/reducers/ipc.js index 0b3f7a8f..2afcdff5 100644 --- a/app/reducers/ipc.js +++ b/app/reducers/ipc.js @@ -31,6 +31,8 @@ import { newTransaction } from './transaction' +import { receiveDescribeNetwork, receiveQueryRoutes } from './network' + // Import all receiving IPC event handlers and pass them into createIpc const ipc = createIpc({ lndSyncing, @@ -79,7 +81,10 @@ const ipc = createIpc({ receiveTransactions, transactionSuccessful, transactionError, - newTransaction + newTransaction, + + receiveDescribeNetwork, + receiveQueryRoutes }) export default ipc diff --git a/app/reducers/network.js b/app/reducers/network.js new file mode 100644 index 00000000..ff8417dc --- /dev/null +++ b/app/reducers/network.js @@ -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 +} diff --git a/app/routes.js b/app/routes.js index 5e3bb10b..d700389d 100644 --- a/app/routes.js +++ b/app/routes.js @@ -4,11 +4,13 @@ import { Switch, Route } from 'react-router' import App from './routes/app' import Activity from './routes/activity' import Wallet from './routes/wallet' +import Channels from './routes/channels' export default () => ( <App> <Switch> <Route path='/wallet' component={Wallet} /> + <Route path='/channels' component={Channels} /> <Route path='/' component={Activity} /> </Switch> </App> diff --git a/app/routes/app/components/App.js b/app/routes/app/components/App.js index 3b0bee55..8f71b14a 100644 --- a/app/routes/app/components/App.js +++ b/app/routes/app/components/App.js @@ -57,7 +57,8 @@ class App extends Component { ) } - if (!currentTicker) { return <LoadingBolt /> } + if (!currentTicker || balance.balanceLoading) { return <LoadingBolt /> } + // return <LoadingBolt /> return ( <div> diff --git a/app/routes/channels/components/Channels.js b/app/routes/channels/components/Channels.js new file mode 100644 index 00000000..f46ec7fd --- /dev/null +++ b/app/routes/channels/components/Channels.js @@ -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 diff --git a/app/routes/channels/components/Channels.scss b/app/routes/channels/components/Channels.scss new file mode 100644 index 00000000..56d354e2 --- /dev/null +++ b/app/routes/channels/components/Channels.scss @@ -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; + } +} diff --git a/app/routes/channels/containers/ChannelsContainer.js b/app/routes/channels/containers/ChannelsContainer.js new file mode 100644 index 00000000..a5cb4e8f --- /dev/null +++ b/app/routes/channels/containers/ChannelsContainer.js @@ -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)) diff --git a/app/routes/channels/index.js b/app/routes/channels/index.js new file mode 100644 index 00000000..b9ed6125 --- /dev/null +++ b/app/routes/channels/index.js @@ -0,0 +1,3 @@ +import ChannelsContainer from './containers/ChannelsContainer' + +export default ChannelsContainer diff --git a/app/routes/wallet/components/Wallet.js b/app/routes/wallet/components/Wallet.js index 0165795a..cd9517cd 100644 --- a/app/routes/wallet/components/Wallet.js +++ b/app/routes/wallet/components/Wallet.js @@ -1,37 +1,24 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import Channels from 'components/Channels' import Peers from 'components/Peers' import styles from './Wallet.scss' class Wallet extends Component { componentWillMount() { - const { fetchPeers, fetchChannels } = this.props + const { fetchPeers } = this.props fetchPeers() - fetchChannels() } render() { const { - ticker, peers: { peersLoading, peers, peer, peerForm }, - channels: { channelsLoading, channels, channel, channelForm, pendingChannels }, fetchPeers, - fetchChannels, setPeer, - setChannel, peerModalOpen, - channelModalOpen, setPeerForm, - setChannelForm, connectRequest, - disconnectRequest, - allChannels, - openChannel, - closeChannel, - currentTicker, - explorerLinkBase + disconnectRequest } = this.props return ( @@ -49,24 +36,6 @@ class Wallet extends Component { connect={connectRequest} disconnect={disconnectRequest} /> - <Channels - fetchChannels={fetchChannels} - ticker={ticker} - peers={peers} - channelsLoading={channelsLoading} - allChannels={allChannels} - channels={channels} - pendingChannels={pendingChannels} - modalChannel={channel} - setChannel={setChannel} - channelModalOpen={channelModalOpen} - channelForm={channelForm} - setChannelForm={setChannelForm} - openChannel={openChannel} - closeChannel={closeChannel} - currentTicker={currentTicker} - explorerLinkBase={explorerLinkBase} - /> </section> </div> ) @@ -75,23 +44,12 @@ class Wallet extends Component { Wallet.propTypes = { fetchPeers: PropTypes.func.isRequired, - fetchChannels: PropTypes.func.isRequired, - ticker: PropTypes.object.isRequired, peers: PropTypes.object.isRequired, - channels: PropTypes.object.isRequired, setPeer: PropTypes.func.isRequired, - setChannel: PropTypes.func.isRequired, peerModalOpen: PropTypes.bool.isRequired, - channelModalOpen: PropTypes.bool.isRequired, setPeerForm: PropTypes.func.isRequired, - setChannelForm: PropTypes.func.isRequired, connectRequest: PropTypes.func.isRequired, - disconnectRequest: PropTypes.func.isRequired, - allChannels: PropTypes.array.isRequired, - openChannel: PropTypes.func.isRequired, - closeChannel: PropTypes.func.isRequired, - currentTicker: PropTypes.object.isRequired, - explorerLinkBase: PropTypes.string.isRequired + disconnectRequest: PropTypes.func.isRequired } diff --git a/app/routes/wallet/containers/WalletContainer.js b/app/routes/wallet/containers/WalletContainer.js index fb7879d0..999163a8 100644 --- a/app/routes/wallet/containers/WalletContainer.js +++ b/app/routes/wallet/containers/WalletContainer.js @@ -12,7 +12,6 @@ import { } from 'reducers/peers' import { fetchChannels, - fetchPendingChannels, setChannel, channelsSelectors, setChannelForm, @@ -30,7 +29,6 @@ const mapDispatchToProps = { disconnectRequest, fetchChannels, - fetchPendingChannels, setChannel, openChannel, closeChannel, @@ -47,8 +45,6 @@ const mapStateToProps = state => ({ peers: state.peers, channels: state.channels, - allChannels: channelsSelectors.allChannels(state), - peerModalOpen: peersSelectors.peerModalOpen(state), channelModalOpen: channelsSelectors.channelModalOpen(state), diff --git a/package.json b/package.json index 55d254a6..07df4197 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "files": [ "dist/", "resources/", + "resources/icons/wallet.svg", + "resources/icons/peers.svg", "node_modules/", "app.html", "main.prod.js", @@ -45,7 +47,6 @@ "rpc.proto" ], "dmg": { - "icon": "./resources/zap_2.icns", "contents": [ { "x": 130, @@ -60,7 +61,6 @@ ] }, "win": { - "icon": "./resources/zap_2.ico", "target": [ "nsis" ] @@ -77,11 +77,8 @@ }, "extraResources": [ "**/resources/bin/darwin/lnd", - "**/resources/bin/linux/lnd", - "**/resources/bin/win32/lnd", - "**/resources/scripts/*", - "**/resources/zap_2.svg", - "**/resources/cloudbolt.svg" + "**/resources/scripts/darwin_generate_certs.sh", + "**/resources/icons/*" ] }, "repository": { @@ -221,6 +218,7 @@ "react-router-redux": "^5.0.0-alpha.6", "react-svg": "^2.1.21", "react-svg-morph": "^0.1.10", + "react-vis-force": "^0.3.1", "react-websocket": "^1.1.7", "redux": "^3.7.1", "redux-electron-ipc": "^1.1.10", diff --git a/resources/bin/darwin/lnd b/resources/bin/darwin/lnd index 4c4247ec..20d6e893 100755 Binary files a/resources/bin/darwin/lnd and b/resources/bin/darwin/lnd differ diff --git a/resources/bin/linux/lnd b/resources/bin/linux/lnd index 83940272..0c2bfab6 100755 Binary files a/resources/bin/linux/lnd and b/resources/bin/linux/lnd differ diff --git a/resources/bin/win32/lnd.exe b/resources/bin/win32/lnd.exe index bc0df569..c6202c8f 100755 Binary files a/resources/bin/win32/lnd.exe and b/resources/bin/win32/lnd.exe differ diff --git a/resources/check.svg b/resources/check.svg deleted file mode 100644 index 4630a65e..00000000 --- a/resources/check.svg +++ /dev/null @@ -1,6 +0,0 @@ -<svg viewBox="0 0 300 300"> - <g> - <path d="M 150, 150 m -0, -150 a -150,-150 0 1,0 0,300 a -150,-150 0 1,0 0,-300" fill="none" stroke-width="19" class="circle animatable reverse" stroke-dashoffset="942.6107177734375" stroke-dasharray="942.6107177734375"></path> - <path fill="none" d="M 90,160 l 45,45 l 90,-95" stroke-width="19" class="check animatable" stroke-dashoffset="194.50213623046875" stroke-dasharray="194.50213623046875"></path> - </g> -</svg> \ No newline at end of file diff --git a/resources/cloudbolt.svg b/resources/cloudbolt.svg deleted file mode 100644 index c35acce3..00000000 --- a/resources/cloudbolt.svg +++ /dev/null @@ -1,47 +0,0 @@ -<?xml version="1.0" encoding="iso-8859-1"?> -<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - width="79.536px" height="79.536px" viewBox="0 0 79.536 79.536" style="enable-background:new 0 0 79.536 79.536;" - xml:space="preserve"> -<g> - <g> - <path class="load-draw-svg" style="fill:#010002;" d="M78.967,29.396c0,8.124-6.582,14.7-14.706,14.7c-0.58,0-6.131,0-13.489,0 - c-4.464,0-9.574,0-14.685,0c-12.896,0-25.626,0-26.942,0c-4.735,0-8.575-1.799-8.575-6.54c0-4.735,3.84-8.575,8.575-8.575 - c0.857,0,1.675,0.171,2.47,0.409c0-0.067-0.021-0.132-0.021-0.202c0-5.525,4.479-9.999,10.004-9.999 - c0.228,0,0.456,0.052,0.687,0.067c-0.013-0.233-0.075-0.451-0.075-0.684C22.209,8.313,30.522,0,40.788,0 - c9.264,0,16.896,6.814,18.289,15.689c1.61-0.616,3.351-0.991,5.184-0.991C72.385,14.698,78.967,21.279,78.967,29.396z - M49.177,47.618H34.504c-4.306,4.329-11.283,11.34-11.363,11.34h11.757L23.146,79.536l26.45-23.52h-8.818L49.177,47.618z"/> - </g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -<g> -</g> -</svg> diff --git a/resources/thunderstorm.svg b/resources/thunderstorm.svg deleted file mode 100644 index 30c98b71..00000000 --- a/resources/thunderstorm.svg +++ /dev/null @@ -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> \ No newline at end of file diff --git a/resources/zap_1.svg b/resources/zap_1.svg deleted file mode 100644 index 7bf6bb84..00000000 --- a/resources/zap_1.svg +++ /dev/null @@ -1,51 +0,0 @@ - - <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="128" height="128" viewBox="0 0 100 100"> - - - <defs> - <g id="background"> -<path fill="url(#background-gradient)" stroke="none" d=" M 90.75 23.05 Q 89.5 23.05 88.85 24.15 88.15 25.25 88.85 26.35 97.25 40.2 95.15 56.15 93.05 71.65 81.75 82.6 70.35 93.7 54.9 95.25 32.95 97.55 17.6 81.85 2.2 66.2 4.85 44.2 6.6 30.2 16.15 19.7 25.65 9.1 39.25 5.8 50.55 3 61.7 5.95 62.85 6.3 63.7 5.65 64.6 5 64.6 3.85 64.6 2.25 62.95 1.7 52.55 -1.1 41.6 0.65 25.55 3.3 14.4 14.85 3.1 26.25 0.65 42.4 -2.95 66.9 14 84.5 30.85 102.1 55.2 99.7 72.6 97.9 85.2 85.4 97.95 72.8 99.7 55.35 101.45 38.55 92.75 24.05 92.05 23.05 90.75 23.05 Z"/> -</g><mask id="mask"> -<path fill="#FFFFFF" stroke="none" d=" M 82.2 82.15 Q 95.5 68.85 95.5 49.95 95.5 31.1 82.2 17.8 68.85 4.5 50 4.5 31.15 4.5 17.8 17.8 4.5 31.1 4.5 49.95 4.5 68.85 17.8 82.15 31.15 95.45 50 95.45 68.85 95.45 82.2 82.15 Z"/> -</mask> - - <linearGradient id="background-gradient" x1="0" y1="0" y2="1" x2="0" > - <stop stop-color="#ebb864" offset="0%"/> - <stop stop-color="#1d1d1d" offset="100%"/> - </linearGradient> - - - - <g transform="scale(2.0833333333333335)" id="picture"><path d="M24 0C10.7 0 0 10.7 0 24s10.7 24 24 24 24-10.7 24-24S37.3 0 24 0zm-7.3 42.7h-.2l6.4-15.9h-8.2L31.9 5.3l-6.7 16.6h8.1L16.7 42.7z"/></g> - - <linearGradient id="picture-gradient" x1="0" y1="0" y2="1" x2="0" > - <stop stop-color="#ebb864" offset="0%"/> - <stop stop-color="#1d1d1d" offset="100%"/> - </linearGradient> - - </defs> - - - - <use xlink:href="#background" fill="url(#background-gradient)" /> - - - - <g mask="url(#mask)"> - <g transform=" - translate( - 50 50 -) - translate(0 0) scale(0.5) - translate( - -50 -50 -)"> - <use xlink:href="#picture" fill="url(#picture-gradient)" /> - </g> - </g> - - - - - - </svg> diff --git a/resources/zap_2.icns b/resources/zap_2.icns deleted file mode 100644 index e2efdc3e..00000000 Binary files a/resources/zap_2.icns and /dev/null differ diff --git a/resources/zap_2.ico b/resources/zap_2.ico deleted file mode 100644 index b6f2b85b..00000000 Binary files a/resources/zap_2.ico and /dev/null differ diff --git a/resources/zap_2.png b/resources/zap_2.png deleted file mode 100644 index bfba8661..00000000 Binary files a/resources/zap_2.png and /dev/null differ diff --git a/resources/zap_3.svg b/resources/zap_3.svg deleted file mode 100644 index a69f06d7..00000000 --- a/resources/zap_3.svg +++ /dev/null @@ -1,51 +0,0 @@ - - <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="128" height="128" viewBox="0 0 100 100"> - - - <defs> - <g id="background"> -<path fill="url(#background-gradient)" stroke="none" d=" M 90.75 23.05 Q 89.5 23.05 88.85 24.15 88.15 25.25 88.85 26.35 97.25 40.2 95.15 56.15 93.05 71.65 81.75 82.6 70.35 93.7 54.9 95.25 32.95 97.55 17.6 81.85 2.2 66.2 4.85 44.2 6.6 30.2 16.15 19.7 25.65 9.1 39.25 5.8 50.55 3 61.7 5.95 62.85 6.3 63.7 5.65 64.6 5 64.6 3.85 64.6 2.25 62.95 1.7 52.55 -1.1 41.6 0.65 25.55 3.3 14.4 14.85 3.1 26.25 0.65 42.4 -2.95 66.9 14 84.5 30.85 102.1 55.2 99.7 72.6 97.9 85.2 85.4 97.95 72.8 99.7 55.35 101.45 38.55 92.75 24.05 92.05 23.05 90.75 23.05 Z"/> -</g><mask id="mask"> -<path fill="#FFFFFF" stroke="none" d=" M 82.2 82.15 Q 95.5 68.85 95.5 49.95 95.5 31.1 82.2 17.8 68.85 4.5 50 4.5 31.15 4.5 17.8 17.8 4.5 31.1 4.5 49.95 4.5 68.85 17.8 82.15 31.15 95.45 50 95.45 68.85 95.45 82.2 82.15 Z"/> -</mask> - - <linearGradient id="background-gradient" x1="0" y1="0" y2="1" x2="0" > - <stop stop-color="#ebb864" offset="0%"/> - <stop stop-color="#1d1d1d" offset="100%"/> - </linearGradient> - - - - <g transform="scale(2.0833333333333335)" id="picture"><path d="M24 0C10.7 0 0 10.7 0 24s10.7 24 24 24 24-10.7 24-24S37.3 0 24 0zM4 24C4 13 13 4 24 4c2.5 0 4.8.5 7 1.3L13.5 27.2h9.3l-6.2 15.3C9.2 39.6 4 32.4 4 24zm20 20c-2.3 0-4.5-.4-6.5-1.1l17-21.2h-9.1l6.5-16C39 8.7 44 15.8 44 24c0 11-9 20-20 20z"/></g> - - <linearGradient id="picture-gradient" x1="0" y1="0" y2="1" x2="0" > - <stop stop-color="#ebb864" offset="0%"/> - <stop stop-color="#1d1d1d" offset="100%"/> - </linearGradient> - - </defs> - - - - <use xlink:href="#background" fill="url(#background-gradient)" /> - - - - <g mask="url(#mask)"> - <g transform=" - translate( - 50 50 -) - translate(0 0) scale(0.55) - translate( - -50 -50 -)"> - <use xlink:href="#picture" fill="url(#picture-gradient)" /> - </g> - </g> - - - - - - </svg> diff --git a/test/components/AnimatedCheckmark.spec.js b/test/components/AnimatedCheckmark.spec.js index 5bc25efe..69727452 100644 --- a/test/components/AnimatedCheckmark.spec.js +++ b/test/components/AnimatedCheckmark.spec.js @@ -3,12 +3,13 @@ import { shallow } from 'enzyme' import Isvg from 'react-inlinesvg' import AnimatedCheckmark from '../../app/components/AnimatedCheckmark' +import checkmarkIcon from '../../app/components/AnimatedCheckmark/checkmark.svg' describe('component.AnimatedCheckmark', () => { describe('default', () => { it('should render default component', () => { const el = shallow(<AnimatedCheckmark />) - expect(el.find(Isvg).props().src).toContain('checkmark.svg') + expect(el.find(Isvg).props().src).toContain(checkmarkIcon) }) }) }) diff --git a/test/components/CryptoIcon.spec.js b/test/components/CryptoIcon.spec.js index a61e53d0..cf05e388 100644 --- a/test/components/CryptoIcon.spec.js +++ b/test/components/CryptoIcon.spec.js @@ -1,9 +1,11 @@ import React from 'react' import { shallow } from 'enzyme' -import { FaBitcoin } from 'react-icons/lib/fa' import Isvg from 'react-inlinesvg' import CryptoIcon from '../../app/components/CryptoIcon' +import skinnyBitcoinIcon from '../../app/icons/skinny_bitcoin.svg' +import litecoinIcon from '../../app/icons/litecoin.svg' + const defaultProps = { currency: 'bch', styles: {} @@ -22,7 +24,7 @@ describe('component.CryptoIcon', () => { const props = { ...defaultProps, currency: 'btc' } const el = shallow(<CryptoIcon {...props} />) it('should show btc symbol', () => { - expect(el.find(FaBitcoin)).toHaveLength(1) + expect(el.find(Isvg).props().src).toContain(skinnyBitcoinIcon) }) }) @@ -30,7 +32,7 @@ describe('component.CryptoIcon', () => { const props = { ...defaultProps, currency: 'ltc' } const el = shallow(<CryptoIcon {...props} />) it('should show ltc symbol', () => { - expect(el.find(Isvg).props().src).toContain('litecoin.svg') + expect(el.find(Isvg).props().src).toContain(litecoinIcon) }) }) }) diff --git a/test/components/LoadingBolt.spec.js b/test/components/LoadingBolt.spec.js index e60cd5f4..156fd466 100644 --- a/test/components/LoadingBolt.spec.js +++ b/test/components/LoadingBolt.spec.js @@ -2,11 +2,12 @@ import React from 'react' import { shallow } from 'enzyme' import Isvg from 'react-inlinesvg' import LoadingBolt from '../../app/components/LoadingBolt' +import cloudboltIcon from '../../app/icons/cloudbolt.svg' describe('component.LoadingBolt', () => { const el = shallow(<LoadingBolt />) it('should show defaults', () => { - expect(el.find(Isvg).props().src).toContain('cloudbolt.svg') + expect(el.find(Isvg).props().src).toContain(cloudboltIcon) expect(el.text()).toContain('loading') }) }) diff --git a/test/components/Nav.spec.js b/test/components/Nav.spec.js index dbe0e4cb..7f98da30 100644 --- a/test/components/Nav.spec.js +++ b/test/components/Nav.spec.js @@ -1,9 +1,6 @@ import React from 'react' import { shallow } from 'enzyme' -import ReactSVG from 'react-svg' import { NavLink } from 'react-router-dom' -import { MdAccountBalanceWallet } from 'react-icons/lib/md' -import { FaClockO, FaDollar } from 'react-icons/lib/fa' import Nav from 'components/Nav' const defaultProps = { diff --git a/test/reducers/__snapshots__/channels.spec.js.snap b/test/reducers/__snapshots__/channels.spec.js.snap index f8f621a6..195b780c 100644 --- a/test/reducers/__snapshots__/channels.spec.js.snap +++ b/test/reducers/__snapshots__/channels.spec.js.snap @@ -12,6 +12,33 @@ Object { "channels": Array [], "channelsLoading": true, "closingChannel": false, + "filter": Object { + "key": "ALL_CHANNELS", + "name": "All Channels", + }, + "filterPulldown": false, + "filters": Array [ + Object { + "key": "ALL_CHANNELS", + "name": "All Channels", + }, + Object { + "key": "ACTIVE_CHANNELS", + "name": "Active Channels", + }, + Object { + "key": "OPEN_CHANNELS", + "name": "Open Channels", + }, + Object { + "key": "OPEN_PENDING_CHANNELS", + "name": "Open Pending Channels", + }, + Object { + "key": "CLOSING_PENDING_CHANNELS", + "name": "Closing Pending Channels", + }, + ], "openingChannel": false, "pendingChannels": Object { "pending_closing_channels": Array [], @@ -19,6 +46,8 @@ Object { "pending_open_channels": Array [], "total_limbo_balance": "", }, + "searchQuery": "", + "viewType": 0, } `; @@ -34,6 +63,33 @@ Object { "channels": Array [], "channelsLoading": false, "closingChannel": false, + "filter": Object { + "key": "ALL_CHANNELS", + "name": "All Channels", + }, + "filterPulldown": false, + "filters": Array [ + Object { + "key": "ALL_CHANNELS", + "name": "All Channels", + }, + Object { + "key": "ACTIVE_CHANNELS", + "name": "Active Channels", + }, + Object { + "key": "OPEN_CHANNELS", + "name": "Open Channels", + }, + Object { + "key": "OPEN_PENDING_CHANNELS", + "name": "Open Pending Channels", + }, + Object { + "key": "CLOSING_PENDING_CHANNELS", + "name": "Closing Pending Channels", + }, + ], "openingChannel": true, "pendingChannels": Object { "pending_closing_channels": Array [], @@ -41,6 +97,8 @@ Object { "pending_open_channels": Array [], "total_limbo_balance": "", }, + "searchQuery": "", + "viewType": 0, } `; @@ -59,11 +117,40 @@ Object { ], "channelsLoading": false, "closingChannel": false, + "filter": Object { + "key": "ALL_CHANNELS", + "name": "All Channels", + }, + "filterPulldown": false, + "filters": Array [ + Object { + "key": "ALL_CHANNELS", + "name": "All Channels", + }, + Object { + "key": "ACTIVE_CHANNELS", + "name": "Active Channels", + }, + Object { + "key": "OPEN_CHANNELS", + "name": "Open Channels", + }, + Object { + "key": "OPEN_PENDING_CHANNELS", + "name": "Open Pending Channels", + }, + Object { + "key": "CLOSING_PENDING_CHANNELS", + "name": "Closing Pending Channels", + }, + ], "openingChannel": false, "pendingChannels": Array [ 3, 4, ], + "searchQuery": "", + "viewType": 0, } `; @@ -79,6 +166,33 @@ Object { "channels": Array [], "channelsLoading": false, "closingChannel": false, + "filter": Object { + "key": "ALL_CHANNELS", + "name": "All Channels", + }, + "filterPulldown": false, + "filters": Array [ + Object { + "key": "ALL_CHANNELS", + "name": "All Channels", + }, + Object { + "key": "ACTIVE_CHANNELS", + "name": "Active Channels", + }, + Object { + "key": "OPEN_CHANNELS", + "name": "Open Channels", + }, + Object { + "key": "OPEN_PENDING_CHANNELS", + "name": "Open Pending Channels", + }, + Object { + "key": "CLOSING_PENDING_CHANNELS", + "name": "Closing Pending Channels", + }, + ], "openingChannel": false, "pendingChannels": Object { "pending_closing_channels": Array [], @@ -86,6 +200,8 @@ Object { "pending_open_channels": Array [], "total_limbo_balance": "", }, + "searchQuery": "", + "viewType": 0, } `; @@ -101,6 +217,33 @@ Object { "channels": Array [], "channelsLoading": false, "closingChannel": false, + "filter": Object { + "key": "ALL_CHANNELS", + "name": "All Channels", + }, + "filterPulldown": false, + "filters": Array [ + Object { + "key": "ALL_CHANNELS", + "name": "All Channels", + }, + Object { + "key": "ACTIVE_CHANNELS", + "name": "Active Channels", + }, + Object { + "key": "OPEN_CHANNELS", + "name": "Open Channels", + }, + Object { + "key": "OPEN_PENDING_CHANNELS", + "name": "Open Pending Channels", + }, + Object { + "key": "CLOSING_PENDING_CHANNELS", + "name": "Closing Pending Channels", + }, + ], "openingChannel": false, "pendingChannels": Object { "pending_closing_channels": Array [], @@ -108,6 +251,8 @@ Object { "pending_open_channels": Array [], "total_limbo_balance": "", }, + "searchQuery": "", + "viewType": 0, } `; @@ -123,6 +268,33 @@ Object { "channels": Array [], "channelsLoading": false, "closingChannel": false, + "filter": Object { + "key": "ALL_CHANNELS", + "name": "All Channels", + }, + "filterPulldown": false, + "filters": Array [ + Object { + "key": "ALL_CHANNELS", + "name": "All Channels", + }, + Object { + "key": "ACTIVE_CHANNELS", + "name": "Active Channels", + }, + Object { + "key": "OPEN_CHANNELS", + "name": "Open Channels", + }, + Object { + "key": "OPEN_PENDING_CHANNELS", + "name": "Open Pending Channels", + }, + Object { + "key": "CLOSING_PENDING_CHANNELS", + "name": "Closing Pending Channels", + }, + ], "openingChannel": false, "pendingChannels": Object { "pending_closing_channels": Array [], @@ -130,5 +302,7 @@ Object { "pending_open_channels": Array [], "total_limbo_balance": "", }, + "searchQuery": "", + "viewType": 0, } `; diff --git a/webpack.config.renderer.dev.js b/webpack.config.renderer.dev.js index b76b554c..02625bed 100644 --- a/webpack.config.renderer.dev.js +++ b/webpack.config.renderer.dev.js @@ -25,7 +25,6 @@ const publicPath = `http://localhost:${port}/dist`; const dll = path.resolve(process.cwd(), 'dll'); const manifest = path.resolve(dll, 'renderer.json'); -console.log('one') /** * Warn if the DLL is not built */ @@ -36,8 +35,6 @@ if (!(fs.existsSync(dll) && fs.existsSync(manifest))) { execSync('npm run build-dll'); } -console.log('two') - export default merge.smart(baseConfig, { devtool: 'inline-source-map', @@ -282,5 +279,3 @@ export default merge.smart(baseConfig, { } } }); - -console.log('three') diff --git a/yarn.lock b/yarn.lock index f5e2db20..35894bd7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2532,6 +2532,31 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" +d3-collection@1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2" + +d3-dispatch@1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8" + +d3-force@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.1.0.tgz#cebf3c694f1078fcc3d4daf8e567b2fbd70d4ea3" + dependencies: + d3-collection "1" + d3-dispatch "1" + d3-quadtree "1" + d3-timer "1" + +d3-quadtree@1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.3.tgz#ac7987e3e23fe805a990f28e1b50d38fcb822438" + +d3-timer@1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531" + d@1: version "1.0.0" resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" @@ -5578,7 +5603,7 @@ lodash.range@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash.range/-/lodash.range-3.2.0.tgz#f461e588f66683f7eadeade513e38a69a565a15d" -lodash.reduce@^4.4.0: +lodash.reduce@^4.4.0, lodash.reduce@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" @@ -7231,6 +7256,15 @@ react-transition-group@^1.2.0: prop-types "^15.5.6" warning "^3.0.0" +react-vis-force@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/react-vis-force/-/react-vis-force-0.3.1.tgz#c7bc96a4e872409f5d4c0fa93fe89c94554d47b7" + dependencies: + d3-force "^1.0.2" + global "^4.3.0" + lodash.reduce "^4.6.0" + prop-types "^15.5.10" + react-websocket@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/react-websocket/-/react-websocket-1.1.7.tgz#0a761f3de354d4731f55343456e03b1f6005b492"