diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index b9891751a..defa06a3d 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -505,3 +505,34 @@ export function doPublish(params) { }); }; } + +export function doAbandonClaim(claimId, txid, nout) { + return function(dispatch, getState) { + const state = getState(); + + dispatch({ + type: types.ABANDON_CLAIM_STARTED, + data: { + claimId: claimId, + txid: txid, + nout: nout, + }, + }); + + const success = dispatch({ + type: types.ABANDON_CLAIM_SUCCEEDED, + data: { + claimId: claimId, + txid: txid, + nout: nout, + }, + }); + + lbry + .claim_abandon({ + txid: txid, + nout: nout, + }) + .then(success); + }; +} diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index 49c949e5c..825dfa07c 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -102,10 +102,15 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { const fileInfo = byOutpoint[outpoint]; if (fileInfo) { + txid = fileInfo.outpoint.slice(0, -2); + nout = fileInfo.outpoint.slice(-1); + dispatch({ type: types.ABANDON_CLAIM_STARTED, data: { claimId: fileInfo.claim_id, + txid: txid, + nout: nout, }, }); @@ -113,9 +118,16 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { type: types.ABANDON_CLAIM_SUCCEEDED, data: { claimId: fileInfo.claim_id, + txid: txid, + nout: nout, }, }); - lbry.claim_abandon({ claim_id: fileInfo.claim_id }).then(success); + lbry + .claim_abandon({ + txid: txid, + nout: nout, + }) + .then(success); } } diff --git a/ui/js/component/transactionList/index.js b/ui/js/component/transactionList/index.js index 5a6c10b6b..39d5cde5e 100644 --- a/ui/js/component/transactionList/index.js +++ b/ui/js/component/transactionList/index.js @@ -1,15 +1,23 @@ import React from "react"; import { connect } from "react-redux"; import { doNavigate } from "actions/navigation"; +import { doAbandonClaim, doResolveUri } from "actions/content"; import { selectClaimedRewardsByTransactionId } from "selectors/rewards"; +import { selectAllMyClaimsByTxidNout } from "selectors/claims"; +import { selectResolvingUris } from "selectors/content"; import TransactionList from "./view"; const select = state => ({ rewards: selectClaimedRewardsByTransactionId(state), + myClaims: selectAllMyClaimsByTxidNout(state), + resolvingUris: selectResolvingUris(state), }); const perform = dispatch => ({ navigate: (path, params) => dispatch(doNavigate(path, params)), + resolveUri: uri => dispatch(doResolveUri(uri)), + abandonClaim: (claimId, txid, nout) => + dispatch(doAbandonClaim(claimId, txid, nout)), }); -export default connect(null, perform)(TransactionList); +export default connect(select, perform)(TransactionList); diff --git a/ui/js/component/transactionList/internal/TransactionListItem.jsx b/ui/js/component/transactionList/internal/TransactionListItem.jsx index fd4429bd3..93e9e56d2 100644 --- a/ui/js/component/transactionList/internal/TransactionListItem.jsx +++ b/ui/js/component/transactionList/internal/TransactionListItem.jsx @@ -6,8 +6,12 @@ import Link from "component/link"; import lbryuri from "lbryuri"; class TransactionListItem extends React.PureComponent { + abandonClaim(abandonData) { + this.props.revokeClaim(abandonData); + } + render() { - const { reward, transaction } = this.props; + const { reward, transaction, isRevokeable } = this.props; const { amount, claim_id: claimId, @@ -16,8 +20,16 @@ class TransactionListItem extends React.PureComponent { fee, txid, type, + nout, } = transaction; + const abandonData = { + name: name, + claimId: claimId, + txid: txid, + nout: nout, + }; + const dateFormat = { month: "short", day: "numeric", @@ -80,6 +92,12 @@ class TransactionListItem extends React.PureComponent { + + {isRevokeable && + this.abandonClaim(abandonData)}> + {__("Revoke")} + } + ); } diff --git a/ui/js/component/transactionList/view.jsx b/ui/js/component/transactionList/view.jsx index beb2f8514..48f476eb6 100644 --- a/ui/js/component/transactionList/view.jsx +++ b/ui/js/component/transactionList/view.jsx @@ -1,6 +1,7 @@ import React from "react"; import TransactionListItem from "./internal/TransactionListItem"; import FormField from "component/formField"; +import lbryuri from "lbryuri"; class TransactionList extends React.PureComponent { constructor(props) { @@ -23,6 +24,27 @@ class TransactionList extends React.PureComponent { return !filter || filter == transaction.type; } + isRevokeable(txid, nout) { + // a claim/support/update is revokable if it + // is in my claim list(claim_list_mine) + return this.props.myClaims.has(`${txid}:${nout}`); + } + + revokeClaim(abandonData) { + const { + name: name, + claimId: claimId, + txid: txid, + nout: nout, + } = abandonData; + + const uri = lbryuri.build({ name, claimId }); + this.props.resolveUri(uri); + if (!this.props.resolvingUris.includes(uri)) { + this.props.abandonClaim(claimId, txid, nout); + } + } + render() { const { emptyMessage, rewards, transactions } = this.props; @@ -62,6 +84,7 @@ class TransactionList extends React.PureComponent { {__("Type")} {__("Details")} {__("Transaction")} + {__("Action")} @@ -70,6 +93,8 @@ class TransactionList extends React.PureComponent { key={`${t.txid}:${t.nout}`} transaction={t} reward={rewards && rewards[t.txid]} + isRevokeable={this.isRevokeable(t.txid, t.nout)} + revokeClaim={this.revokeClaim.bind(this)} /> )} diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index a3e4ae2b4..2f0a8658f 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -45,6 +45,9 @@ reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) { const byId = Object.assign({}, state.byId); const pendingById = Object.assign({}, state.pendingById); const abandoningById = Object.assign({}, state.abandoningById); + const allMyClaimsByTxidNout = new Set( + claims.map(claim => `${claim.txid}:${claim.nout}`) + ); const myClaims = new Set( claims .map(claim => claim.claim_id) @@ -78,6 +81,7 @@ reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) { return Object.assign({}, state, { isFetchingClaimListMine: false, myClaims: myClaims, + allMyClaimsByTxidNout: allMyClaimsByTxidNout, byId, pendingById, }); @@ -157,21 +161,35 @@ reducers[types.ABANDON_CLAIM_STARTED] = function(state, action) { }; reducers[types.ABANDON_CLAIM_SUCCEEDED] = function(state, action) { - const { claimId } = action.data; + const { claimId, txid, nout } = action.data; const myClaims = new Set(state.myClaims); const byId = Object.assign({}, state.byId); const claimsByUri = Object.assign({}, state.claimsByUri); + const supports = byId[claimId].supports; const uris = []; - Object.keys(claimsByUri).forEach(uri => { - if (claimsByUri[uri] === claimId) { - delete claimsByUri[uri]; - } - }); + // This logic is needed when a claim has supports + // and it is the support that is being abandoned + // so we need to remove the support from the state + // but this is not working, even after calling resolve on the uri. + if (supports && supports.length > 0) { + indexToDelete = supports.findIndex(support => { + return support.txid === txid && support.nout === nout; + }); - delete byId[claimId]; - myClaims.delete(claimId); + supports.splice[(indexToDelete, 1)]; + } + if (!supports || supports.length == 0) { + Object.keys(claimsByUri).forEach(uri => { + if (claimsByUri[uri] === claimId) { + delete claimsByUri[uri]; + } + }); + + delete byId[claimId]; + myClaims.delete(claimId); + } return Object.assign({}, state, { myClaims, byId, diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index c90b78843..608ceae67 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -157,6 +157,11 @@ export const selectMyClaimsWithoutChannels = createSelector( myClaims => myClaims.filter(claim => !claim.name.match(/^@/)) ); +export const selectAllMyClaimsByTxidNout = createSelector( + _selectState, + state => state.allMyClaimsByTxidNout || {} +); + export const selectMyClaimsOutpoints = createSelector( selectMyClaims, myClaims => { diff --git a/ui/scss/component/_table.scss b/ui/scss/component/_table.scss index 06f33aaf0..e17e90253 100644 --- a/ui/scss/component/_table.scss +++ b/ui/scss/component/_table.scss @@ -62,9 +62,10 @@ table.table-stretch { } table.table-transactions { - td:nth-of-type(1) { width: 15%; } - td:nth-of-type(2) { width: 15%; } - td:nth-of-type(3) { width: 15%; } - td:nth-of-type(4) { width: 40%; } - td:nth-of-type(5) { width: 15%; } + td:nth-of-type(1) { width: 13%; } + td:nth-of-type(2) { width: 13%; } + td:nth-of-type(3) { width: 13%; } + td:nth-of-type(4) { width: 35%; } + td:nth-of-type(5) { width: 13%; } + td:nth-of-type(6) { width: 13%; } }