Browse Source

feature(contacts): close contact and close contact loading UX

renovate/lint-staged-8.x
Jack Mallers 7 years ago
parent
commit
28d52af8e6
  1. 20
      app/components/Contacts/ContactModal.js
  2. 42
      app/components/Contacts/ContactModal.scss
  3. 11
      app/components/Contacts/LoadingContact.js
  4. 22
      app/lnd/methods/channelController.js
  5. 48
      app/reducers/channels.js
  6. 9
      app/routes/contacts/components/Contacts.js
  7. 6
      app/routes/contacts/containers/ContactsContainer.js

20
app/components/Contacts/ContactModal.js

@ -8,7 +8,14 @@ import { btc } from 'utils'
import styles from './ContactModal.scss' import styles from './ContactModal.scss'
const ContactModal = ({ isOpen, channel, closeContactModal, channelNodes }) => { const ContactModal = ({
isOpen,
channel,
closeContactModal,
channelNodes,
closeChannel,
closingChannelIds
}) => {
if (!channel) { return <span /> } if (!channel) { return <span /> }
const customStyles = { const customStyles = {
@ -99,7 +106,16 @@ const ContactModal = ({ isOpen, channel, closeContactModal, channelNodes }) => {
</section> </section>
<footer> <footer>
<div>Remove</div> {
closingChannelIds.includes(channel.chan_id) ?
<span className={styles.inactive}>
<div className={styles.loading}>
<div className={styles.spinner} />
</div>
</span>
:
<div onClick={() => closeChannel({ channel_point: channel.channel_point, chan_id: channel.chan_id })}>Remove</div>
}
</footer> </footer>
</div> </div>
} }

42
app/components/Contacts/ContactModal.scss

@ -105,3 +105,45 @@
} }
} }
} }
@-webkit-keyframes animation-rotate {
100% {
-webkit-transform: rotate(360deg);
}
}
@-moz-keyframes animation-rotate {
100% {
-moz-transform: rotate(360deg);
}
}
@-o-keyframes animation-rotate {
100% {
-o-transform: rotate(360deg);
}
}
@keyframes animation-rotate {
100% {
transform: rotate(360deg);
}
}
.spinner {
border: 1px solid rgba(0, 0, 0, 0.1);
border-left-color: rgba(0, 0, 0, 0.4);
-webkit-border-radius: 999px;
-moz-border-radius: 999px;
border-radius: 999px;
}
.spinner {
margin: 0 auto;
height: 20px;
width: 20px;
-webkit-animation: animation-rotate 1000ms linear infinite;
-moz-animation: animation-rotate 1000ms linear infinite;
-o-animation: animation-rotate 1000ms linear infinite;
animation: animation-rotate 1000ms linear infinite;
}

11
app/components/Contacts/LoadingContact.js

@ -4,12 +4,19 @@ import { FaCircle } from 'react-icons/lib/fa'
import { btc } from 'utils' import { btc } from 'utils'
import styles from './Contact.scss' import styles from './Contact.scss'
const LoadingContact = ({ pubkey }) => ( const LoadingContact = ({ pubkey, isClosing }) => (
<li className={`${styles.friend} ${styles.loading}`}> <li className={`${styles.friend} ${styles.loading}`}>
<section className={styles.info}> <section className={styles.info}>
<p> <p>
<FaCircle style={{ verticalAlign: 'top' }} /> <FaCircle style={{ verticalAlign: 'top' }} />
<span>Loading</span> <span>
{
isClosing ?
'Closing'
:
'Loading'
}
</span>
</p> </p>
<h2>{pubkey}</h2> <h2>{pubkey}</h2>
</section> </section>

22
app/lnd/methods/channelController.js

@ -123,20 +123,30 @@ export function listChannels(lnd, meta) {
* @return {[type]} [description] * @return {[type]} [description]
*/ */
export function closeChannel(lnd, meta, event, payload) { export function closeChannel(lnd, meta, event, payload) {
const chan_id = payload.chan_id
const tx = payload.channel_point.funding_txid.match(/.{2}/g).reverse().join('') const tx = payload.channel_point.funding_txid.match(/.{2}/g).reverse().join('')
const res = { const res = {
channel_point: { channel_point: {
funding_txid: BufferUtil.hexToBuffer(tx), funding_txid: BufferUtil.hexToBuffer(tx),
output_index: Number(payload.channel_point.output_index) output_index: Number(payload.channel_point.output_index)
}, },
force: true force: false
} }
return new Promise((resolve, reject) => return new Promise((resolve, reject) => {
pushclosechannel(lnd, meta, event, res) try {
.then(data => resolve(data)) const call = lnd.closeChannel(res, meta)
.catch(error => reject(error))
) call.on('data', data => event.sender.send('pushclosechannelupdated', { data, chan_id }))
call.on('end', () => event.sender.send('pushclosechannelend'))
call.on('error', error => event.sender.send('pushclosechannelerror', { error: error.toString(), chan_id }))
call.on('status', status => event.sender.send('pushclosechannelstatus', { status, chan_id }))
resolve(null, res)
} catch (error) {
reject(error, null)
}
})
} }

48
app/reducers/channels.js

@ -33,6 +33,9 @@ export const CHANGE_CHANNEL_FILTER = 'CHANGE_CHANNEL_FILTER'
export const ADD_LOADING_PUBKEY = 'ADD_LOADING_PUBKEY' export const ADD_LOADING_PUBKEY = 'ADD_LOADING_PUBKEY'
export const REMOVE_LOADING_PUBKEY = 'REMOVE_LOADING_PUBKEY' export const REMOVE_LOADING_PUBKEY = 'REMOVE_LOADING_PUBKEY'
export const ADD_ClOSING_CHAN_ID = 'ADD_ClOSING_CHAN_ID'
export const REMOVE_ClOSING_CHAN_ID = 'REMOVE_ClOSING_CHAN_ID'
export const OPEN_CONTACT_MODAL = 'OPEN_CONTACT_MODAL' export const OPEN_CONTACT_MODAL = 'OPEN_CONTACT_MODAL'
export const CLOSE_CONTACT_MODAL = 'CLOSE_CONTACT_MODAL' export const CLOSE_CONTACT_MODAL = 'CLOSE_CONTACT_MODAL'
@ -112,6 +115,20 @@ export function removeLoadingPubkey(pubkey) {
} }
} }
export function addClosingChanId(chanId) {
return {
type: ADD_ClOSING_CHAN_ID,
chanId
}
}
export function removeClosingChanId(chanId) {
return {
type: REMOVE_ClOSING_CHAN_ID,
chanId
}
}
export function openContactModal(channel) { export function openContactModal(channel) {
return { return {
type: OPEN_CONTACT_MODAL, type: OPEN_CONTACT_MODAL,
@ -185,19 +202,24 @@ export const pushchannelstatus = (event, data) => (dispatch) => { // eslint-disa
} }
// Send IPC event for opening a channel // Send IPC event for opening a channel
export const closeChannel = ({ channel_point }) => (dispatch) => { export const closeChannel = ({ channel_point, chan_id }) => (dispatch) => {
dispatch(closingChannel()) dispatch(closingChannel())
const channelPoint = channel_point.split(':') dispatch(addClosingChanId(chan_id))
const [funding_txid, output_index] = channel_point.split(':')
console.log('funding_txid: ', funding_txid)
console.log('output_index: ', output_index)
console.log('chan_id: ', chan_id)
ipcRenderer.send( ipcRenderer.send(
'lnd', 'lnd',
{ {
msg: 'closeChannel', msg: 'closeChannel',
data: { data: {
channel_point: { channel_point: {
funding_txid: channelPoint[0], funding_txid,
output_index: channelPoint[1] output_index
}, },
force: true chan_id
} }
} }
) )
@ -211,9 +233,11 @@ export const closeChannelSuccessful = (event, data) => (dispatch) => {
} }
// Receive IPC event for updated closing channel // Receive IPC event for updated closing channel
export const pushclosechannelupdated = (event, data) => (dispatch) => { export const pushclosechannelupdated = (event, { data, chan_id }) => (dispatch) => {
console.log('PUSH CLOSE CHANNEL UPDATED: ', data) console.log('PUSH CLOSE CHANNEL UPDATED: ', data)
console.log('PUSH CLOSE CHANNEL chan_id: ', chan_id)
dispatch(fetchChannels()) dispatch(fetchChannels())
dispatch(removeClosingChanId(chan_id))
} }
// Receive IPC event for closing channel end // Receive IPC event for closing channel end
@ -223,9 +247,11 @@ export const pushclosechannelend = (event, data) => (dispatch) => {
} }
// Receive IPC event for closing channel error // Receive IPC event for closing channel error
export const pushclosechannelerror = (event, data) => (dispatch) => { export const pushclosechannelerror = (event, { error, chan_id }) => (dispatch) => {
console.log('PUSH CLOSE CHANNEL END: ', data) console.log('PUSH CLOSE CHANNEL END: ', error)
dispatch(fetchChannels()) console.log('PUSH CLOSE CHANNEL chan_id: ', chan_id)
dispatch(setError(error))
dispatch(removeClosingChanId(chan_id))
} }
// Receive IPC event for closing channel status // Receive IPC event for closing channel status
@ -315,6 +341,9 @@ const ACTION_HANDLERS = {
[ADD_LOADING_PUBKEY]: (state, { pubkey }) => ({ ...state, loadingChannelPubkeys: [pubkey, ...state.loadingChannelPubkeys] }), [ADD_LOADING_PUBKEY]: (state, { pubkey }) => ({ ...state, loadingChannelPubkeys: [pubkey, ...state.loadingChannelPubkeys] }),
[REMOVE_LOADING_PUBKEY]: (state, { pubkey }) => ({ ...state, loadingChannelPubkeys: state.loadingChannelPubkeys.filter(loadingPubkey => loadingPubkey !== pubkey) }), [REMOVE_LOADING_PUBKEY]: (state, { pubkey }) => ({ ...state, loadingChannelPubkeys: state.loadingChannelPubkeys.filter(loadingPubkey => loadingPubkey !== pubkey) }),
[ADD_ClOSING_CHAN_ID]: (state, { chanId }) => ({ ...state, closingChannelIds: [chanId, ...state.closingChannelIds] }),
[REMOVE_ClOSING_CHAN_ID]: (state, { chanId }) => ({ ...state, closingChannelIds: state.closingChannelIds.filter(closingChanId => closingChanId !== chanId) }),
[OPEN_CONTACT_MODAL]: (state, { channel }) => ({ ...state, contactModal: { isOpen: true, channel } }), [OPEN_CONTACT_MODAL]: (state, { channel }) => ({ ...state, contactModal: { isOpen: true, channel } }),
[CLOSE_CONTACT_MODAL]: state => ({ ...state, contactModal: { isOpen: false, channel: null } }) [CLOSE_CONTACT_MODAL]: state => ({ ...state, contactModal: { isOpen: false, channel: null } })
@ -489,6 +518,7 @@ const initialState = {
], ],
loadingChannelPubkeys: [], loadingChannelPubkeys: [],
closingChannelIds: [],
contactModal: { contactModal: {
isOpen: false, isOpen: false,

9
app/routes/contacts/components/Contacts.js

@ -43,7 +43,8 @@ class Contacts extends Component {
filterPulldown, filterPulldown,
filter, filter,
viewType, viewType,
loadingChannelPubkeys loadingChannelPubkeys,
closingChannelIds
}, },
currentChannels, currentChannels,
activeChannels, activeChannels,
@ -156,12 +157,14 @@ class Contacts extends Component {
<ul className={`${styles.friends} ${filterPulldown && styles.fade}`}> <ul className={`${styles.friends} ${filterPulldown && styles.fade}`}>
{ {
loadingChannelPubkeys.map(pubkey => <LoadingContact pubkey={pubkey} />) loadingChannelPubkeys.map(pubkey => <LoadingContact pubkey={pubkey} isClosing={false} />)
} }
{ {
currentChannels.length > 0 && currentChannels.map((channel, index) => { currentChannels.length > 0 && currentChannels.map((channel, index) => {
if (Object.prototype.hasOwnProperty.call(channel, 'blocks_till_open')) { if (closingChannelIds.includes(channel.chan_id)) {
return <LoadingContact pubkey={channel.remote_pubkey} isClosing={true} />
} else if (Object.prototype.hasOwnProperty.call(channel, 'blocks_till_open')) {
return <PendingContact channel={channel} key={index} /> return <PendingContact channel={channel} key={index} />
} else if (Object.prototype.hasOwnProperty.call(channel, 'closing_txid')) { } else if (Object.prototype.hasOwnProperty.call(channel, 'closing_txid')) {
return <ClosingContact channel={channel} key={index} /> return <ClosingContact channel={channel} key={index} />

6
app/routes/contacts/containers/ContactsContainer.js

@ -4,6 +4,7 @@ import { connect } from 'react-redux'
import { import {
fetchChannels, fetchChannels,
openChannel, openChannel,
closeChannel,
updateChannelSearchQuery, updateChannelSearchQuery,
toggleFilterPulldown, toggleFilterPulldown,
@ -37,6 +38,7 @@ const mapDispatchToProps = {
updateContactFormSearchQuery, updateContactFormSearchQuery,
updateContactCapacity, updateContactCapacity,
openChannel, openChannel,
closeChannel,
updateChannelSearchQuery, updateChannelSearchQuery,
toggleFilterPulldown, toggleFilterPulldown,
changeFilter, changeFilter,
@ -70,10 +72,12 @@ const mapStateToProps = state => ({
const mergeProps = (stateProps, dispatchProps, ownProps) => { const mergeProps = (stateProps, dispatchProps, ownProps) => {
const contactModalProps = { const contactModalProps = {
closeContactModal: dispatchProps.closeContactModal, closeContactModal: dispatchProps.closeContactModal,
closeChannel: dispatchProps.closeChannel,
isOpen: stateProps.channels.contactModal.isOpen, isOpen: stateProps.channels.contactModal.isOpen,
channel: stateProps.channels.contactModal.channel, channel: stateProps.channels.contactModal.channel,
channelNodes: stateProps.channelNodes channelNodes: stateProps.channelNodes,
closingChannelIds: stateProps.channels.closingChannelIds
} }
const contactsFormProps = { const contactsFormProps = {

Loading…
Cancel
Save