Merge branch 'revoke_claims'
This commit is contained in:
commit
8987fca22c
15 changed files with 237 additions and 34 deletions
|
@ -8,6 +8,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
|||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
* Now you can revoke your claims from the txns list itself.(#581)
|
||||
* The app now closes to the system tray unless specifically requested to quit. (#374)
|
||||
* Added new window menu options for reloading and help.
|
||||
* Rewards are now marked in transaction history (#660)
|
||||
|
|
|
@ -4,7 +4,7 @@ import lbry from "lbry";
|
|||
import lbryio from "lbryio";
|
||||
import lbryuri from "lbryuri";
|
||||
import { makeSelectClientSetting } from "selectors/settings";
|
||||
import { selectBalance } from "selectors/wallet";
|
||||
import { selectBalance, selectTransactionItems } from "selectors/wallet";
|
||||
import {
|
||||
makeSelectFileInfoForUri,
|
||||
selectDownloadingByOutpoint,
|
||||
|
@ -495,3 +495,46 @@ export function doPublish(params) {
|
|||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doAbandonClaim(txid, nout) {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
const transactionItems = selectTransactionItems(state);
|
||||
const { claim_id: claimId, claim_name: name } = transactionItems.find(
|
||||
claim => claim.txid == txid && claim.nout == nout
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: types.ABANDON_CLAIM_STARTED,
|
||||
data: {
|
||||
claimId: claimId,
|
||||
},
|
||||
});
|
||||
|
||||
const errorCallback = error => {
|
||||
dispatch(doOpenModal(modals.TRANSACTION_FAILED));
|
||||
};
|
||||
|
||||
const successCallback = results => {
|
||||
if (results.txid) {
|
||||
dispatch({
|
||||
type: types.ABANDON_CLAIM_SUCCEEDED,
|
||||
data: {
|
||||
claimId: claimId,
|
||||
},
|
||||
});
|
||||
dispatch(doResolveUri(lbryuri.build({ name, claimId })));
|
||||
dispatch(doFetchClaimListMine());
|
||||
} else {
|
||||
dispatch(doOpenModal(modals.TRANSACTION_FAILED));
|
||||
}
|
||||
};
|
||||
|
||||
lbry
|
||||
.claim_abandon({
|
||||
txid: txid,
|
||||
nout: nout,
|
||||
})
|
||||
.then(successCallback, errorCallback);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as types from "constants/action_types";
|
||||
import lbry from "lbry";
|
||||
import { doFetchClaimListMine } from "actions/content";
|
||||
import { doFetchClaimListMine, doAbandonClaim } from "actions/content";
|
||||
import {
|
||||
selectClaimsByUri,
|
||||
selectIsFetchingClaimListMine,
|
||||
|
@ -102,20 +102,10 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) {
|
|||
const fileInfo = byOutpoint[outpoint];
|
||||
|
||||
if (fileInfo) {
|
||||
dispatch({
|
||||
type: types.ABANDON_CLAIM_STARTED,
|
||||
data: {
|
||||
claimId: fileInfo.claim_id,
|
||||
},
|
||||
});
|
||||
txid = fileInfo.outpoint.slice(0, -2);
|
||||
nout = fileInfo.outpoint.slice(-1);
|
||||
|
||||
const success = dispatch({
|
||||
type: types.ABANDON_CLAIM_SUCCEEDED,
|
||||
data: {
|
||||
claimId: fileInfo.claim_id,
|
||||
},
|
||||
});
|
||||
lbry.claim_abandon({ claim_id: fileInfo.claim_id }).then(success);
|
||||
dispatch(doAbandonClaim(txid, nout));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { doNavigate } from "actions/navigation";
|
||||
import { doOpenModal } from "actions/app";
|
||||
import { selectClaimedRewardsByTransactionId } from "selectors/rewards";
|
||||
import { selectAllMyClaimsByOutpoint } from "selectors/claims";
|
||||
import TransactionList from "./view";
|
||||
|
||||
const select = state => ({
|
||||
rewards: selectClaimedRewardsByTransactionId(state),
|
||||
myClaims: selectAllMyClaimsByOutpoint(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(TransactionList);
|
||||
|
|
|
@ -4,14 +4,41 @@ import { CreditAmount } from "component/common";
|
|||
import DateTime from "component/dateTime";
|
||||
import Link from "component/link";
|
||||
import lbryuri from "lbryuri";
|
||||
import * as txnTypes from "constants/transaction_types";
|
||||
|
||||
class TransactionListItem extends React.PureComponent {
|
||||
abandonClaim() {
|
||||
const { txid, nout } = this.props.transaction;
|
||||
|
||||
this.props.revokeClaim(txid, nout);
|
||||
}
|
||||
|
||||
getLink(type) {
|
||||
if (type == txnTypes.TIP) {
|
||||
return (
|
||||
<Link
|
||||
onClick={this.abandonClaim.bind(this)}
|
||||
icon="icon-unlock-alt"
|
||||
title={__("Unlock")}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Link
|
||||
onClick={this.abandonClaim.bind(this)}
|
||||
icon="icon-trash"
|
||||
title={__("Revoke")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
capitalize(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { reward, transaction } = this.props;
|
||||
const { reward, transaction, isRevokeable } = this.props;
|
||||
const {
|
||||
amount,
|
||||
claim_id: claimId,
|
||||
|
@ -20,6 +47,7 @@ class TransactionListItem extends React.PureComponent {
|
|||
fee,
|
||||
txid,
|
||||
type,
|
||||
nout,
|
||||
} = transaction;
|
||||
|
||||
const dateFormat = {
|
||||
|
@ -43,7 +71,7 @@ class TransactionListItem extends React.PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
: <span className="empty">
|
||||
{__("(Transaction pending)")}
|
||||
{__("Pending")}
|
||||
</span>}
|
||||
</td>
|
||||
<td>
|
||||
|
@ -64,7 +92,9 @@ class TransactionListItem extends React.PureComponent {
|
|||
/>}
|
||||
</td>
|
||||
<td>
|
||||
{this.capitalize(type)}
|
||||
{this.capitalize(type)}{" "}
|
||||
{isRevokeable && this.getLink(type)}
|
||||
|
||||
</td>
|
||||
<td>
|
||||
{reward &&
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import React from "react";
|
||||
import TransactionListItem from "./internal/TransactionListItem";
|
||||
import FormField from "component/formField";
|
||||
import Link from "component/link";
|
||||
import * as icons from "constants/icons";
|
||||
import * as modals from "constants/modal_types";
|
||||
|
||||
class TransactionList extends React.PureComponent {
|
||||
constructor(props) {
|
||||
|
@ -23,6 +26,16 @@ 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(txid, nout) {
|
||||
this.props.openModal(modals.CONFIRM_CLAIM_REVOKE, { txid, nout });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { emptyMessage, rewards, transactions } = this.props;
|
||||
|
||||
|
@ -48,6 +61,11 @@ class TransactionList extends React.PureComponent {
|
|||
<option value="support">{__("Supports")}</option>
|
||||
<option value="update">{__("Updates")}</option>
|
||||
</FormField>
|
||||
{" "}
|
||||
<Link
|
||||
href="https://lbry.io/faq/transaction-types"
|
||||
icon={icons.HELP_CIRCLE}
|
||||
/>
|
||||
</span>}
|
||||
{!transactionList.length &&
|
||||
<div className="empty">
|
||||
|
@ -70,6 +88,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)}
|
||||
/>
|
||||
)}
|
||||
</tbody>
|
||||
|
|
|
@ -73,9 +73,7 @@ class Video extends React.PureComponent {
|
|||
"It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds."
|
||||
);
|
||||
} else if (isLoading) {
|
||||
loadStatusMessage = __(
|
||||
"Requesting stream..."
|
||||
);
|
||||
loadStatusMessage = __("Requesting stream...");
|
||||
} else if (isDownloading) {
|
||||
loadStatusMessage = __("Downloading stream... not long left now!");
|
||||
}
|
||||
|
|
|
@ -2,3 +2,4 @@ export const FEATURED = "rocket";
|
|||
export const LOCAL = "folder";
|
||||
export const FILE = "file";
|
||||
export const HISTORY = "history";
|
||||
export const HELP_CIRCLE = "question-circle";
|
||||
|
|
|
@ -13,3 +13,4 @@ export const INSUFFICIENT_BALANCE = "insufficient_balance";
|
|||
export const REWARD_APPROVAL_REQUIRED = "reward_approval_required";
|
||||
export const AFFIRM_PURCHASE = "affirm_purchase";
|
||||
export const CREDIT_INTRO = "credit_intro";
|
||||
export const CONFIRM_CLAIM_REVOKE = "confirmClaimRevoke";
|
||||
|
|
1
ui/js/constants/transaction_types.js
Normal file
1
ui/js/constants/transaction_types.js
Normal file
|
@ -0,0 +1 @@
|
|||
export const TIP = "tip";
|
17
ui/js/modal/modalRevokeClaim/index.js
Normal file
17
ui/js/modal/modalRevokeClaim/index.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { doCloseModal } from "actions/app";
|
||||
import { doAbandonClaim } from "actions/content";
|
||||
import { selectTransactionItems } from "selectors/wallet";
|
||||
import ModalRevokeClaim from "./view";
|
||||
|
||||
const select = state => ({
|
||||
transactionItems: selectTransactionItems(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
closeModal: () => dispatch(doCloseModal()),
|
||||
abandonClaim: (txid, nout) => dispatch(doAbandonClaim(txid, nout)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(ModalRevokeClaim);
|
86
ui/js/modal/modalRevokeClaim/view.jsx
Normal file
86
ui/js/modal/modalRevokeClaim/view.jsx
Normal file
|
@ -0,0 +1,86 @@
|
|||
import React from "react";
|
||||
import { Modal } from "modal/modal";
|
||||
import * as txnTypes from "constants/transaction_types";
|
||||
|
||||
class ModalRevokeClaim extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
revokeClaim() {
|
||||
const { txid, nout } = this.props;
|
||||
|
||||
this.props.closeModal();
|
||||
this.props.abandonClaim(txid, nout);
|
||||
}
|
||||
|
||||
getButtonLabel(type) {
|
||||
if (type == txnTypes.TIP) {
|
||||
return "Confirm Tip Unlock";
|
||||
} else {
|
||||
return "Confirm Claim Revoke";
|
||||
}
|
||||
}
|
||||
|
||||
getMsgBody(type) {
|
||||
if (type == txnTypes.TIP) {
|
||||
return (
|
||||
<div>
|
||||
<h3>{__("Confirm Tip Unlock")}</h3>
|
||||
<p>
|
||||
{__("Are you sure you want to unlock these credits?")}
|
||||
<br />
|
||||
<br />
|
||||
{__(
|
||||
"These credits are permanently yours and can be\
|
||||
unlocked at any time. Unlocking them allows you to\
|
||||
spend them, but can hurt the performance of your\
|
||||
content in lookups and search results. It is\
|
||||
recommended you leave tips locked until you\
|
||||
need or want to spend them."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<h3>{__("Confirm Claim Revoke")}</h3>
|
||||
<p>
|
||||
{__("Are you sure want to revoke this claim?")}
|
||||
<br />
|
||||
<br />
|
||||
{__(
|
||||
"This will prevent others from resolving and\
|
||||
accessing the content you published. It will return\
|
||||
the LBC to your spendable balance, less a small\
|
||||
transaction fee."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { transactionItems, txid, nout, closeModal } = this.props;
|
||||
const { type } = transactionItems.find(
|
||||
claim => claim.txid == txid && claim.nout == nout
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={true}
|
||||
contentLabel={__("Confirm Claim Revoke")}
|
||||
type="confirm"
|
||||
confirmButtonLabel={this.getButtonLabel(type)}
|
||||
onConfirmed={this.revokeClaim.bind(this)}
|
||||
onAborted={closeModal}
|
||||
>
|
||||
{this.getMsgBody(type)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ModalRevokeClaim;
|
|
@ -13,6 +13,7 @@ import ModalTransactionFailed from "modal/modalTransactionFailed";
|
|||
import ModalInsufficientBalance from "modal/modalInsufficientBalance";
|
||||
import ModalFileTimeout from "modal/modalFileTimeout";
|
||||
import ModalAffirmPurchase from "modal/modalAffirmPurchase";
|
||||
import ModalRevokeClaim from "modal/modalRevokeClaim";
|
||||
import * as modals from "constants/modal_types";
|
||||
|
||||
class ModalRouter extends React.PureComponent {
|
||||
|
@ -132,6 +133,8 @@ class ModalRouter extends React.PureComponent {
|
|||
return <ModalRemoveFile {...modalProps} />;
|
||||
case modals.AFFIRM_PURCHASE:
|
||||
return <ModalAffirmPurchase {...modalProps} />;
|
||||
case modals.CONFIRM_CLAIM_REVOKE:
|
||||
return <ModalRevokeClaim {...modalProps} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -45,11 +45,6 @@ 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 myClaims = new Set(
|
||||
claims
|
||||
.map(claim => claim.claim_id)
|
||||
.filter(claimId => Object.keys(abandoningById).indexOf(claimId) === -1)
|
||||
);
|
||||
|
||||
claims
|
||||
.filter(claim => claim.category && claim.category.match(/claim/))
|
||||
|
@ -77,7 +72,7 @@ reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) {
|
|||
|
||||
return Object.assign({}, state, {
|
||||
isFetchingClaimListMine: false,
|
||||
myClaims: myClaims,
|
||||
myClaims: claims,
|
||||
byId,
|
||||
pendingById,
|
||||
});
|
||||
|
@ -158,10 +153,8 @@ reducers[types.ABANDON_CLAIM_STARTED] = function(state, action) {
|
|||
|
||||
reducers[types.ABANDON_CLAIM_SUCCEEDED] = function(state, action) {
|
||||
const { claimId } = action.data;
|
||||
const myClaims = new Set(state.myClaims);
|
||||
const byId = Object.assign({}, state.byId);
|
||||
const claimsByUri = Object.assign({}, state.claimsByUri);
|
||||
const uris = [];
|
||||
|
||||
Object.keys(claimsByUri).forEach(uri => {
|
||||
if (claimsByUri[uri] === claimId) {
|
||||
|
@ -170,10 +163,8 @@ reducers[types.ABANDON_CLAIM_SUCCEEDED] = function(state, action) {
|
|||
});
|
||||
|
||||
delete byId[claimId];
|
||||
myClaims.delete(claimId);
|
||||
|
||||
return Object.assign({}, state, {
|
||||
myClaims,
|
||||
byId,
|
||||
claimsByUri,
|
||||
});
|
||||
|
|
|
@ -52,7 +52,7 @@ export const makeSelectClaimIsMine = rawUri => {
|
|||
const uri = lbryuri.normalize(rawUri);
|
||||
return createSelector(
|
||||
selectClaimsByUri,
|
||||
selectMyClaimsRaw,
|
||||
selectMyActiveClaims,
|
||||
(claims, myClaims) =>
|
||||
claims &&
|
||||
claims[uri] &&
|
||||
|
@ -123,19 +123,31 @@ export const selectIsFetchingClaimListMine = createSelector(
|
|||
|
||||
export const selectMyClaimsRaw = createSelector(
|
||||
_selectState,
|
||||
state => new Set(state.myClaims)
|
||||
state => state.myClaims
|
||||
);
|
||||
|
||||
export const selectAbandoningIds = createSelector(_selectState, state =>
|
||||
Object.keys(state.abandoningById || {})
|
||||
);
|
||||
|
||||
export const selectMyActiveClaims = createSelector(
|
||||
selectMyClaimsRaw,
|
||||
selectAbandoningIds,
|
||||
(claims, abandoningIds) =>
|
||||
new Set(
|
||||
claims &&
|
||||
claims
|
||||
.map(claim => claim.claim_id)
|
||||
.filter(claimId => Object.keys(abandoningIds).indexOf(claimId) === -1)
|
||||
)
|
||||
);
|
||||
|
||||
export const selectPendingClaims = createSelector(_selectState, state =>
|
||||
Object.values(state.pendingById || {})
|
||||
);
|
||||
|
||||
export const selectMyClaims = createSelector(
|
||||
selectMyClaimsRaw,
|
||||
selectMyActiveClaims,
|
||||
selectClaimsById,
|
||||
selectAbandoningIds,
|
||||
selectPendingClaims,
|
||||
|
@ -157,6 +169,11 @@ export const selectMyClaimsWithoutChannels = createSelector(
|
|||
myClaims => myClaims.filter(claim => !claim.name.match(/^@/))
|
||||
);
|
||||
|
||||
export const selectAllMyClaimsByOutpoint = createSelector(
|
||||
selectMyClaimsRaw,
|
||||
claims => new Set(claims.map(claim => `${claim.txid}:${claim.nout}`))
|
||||
);
|
||||
|
||||
export const selectMyClaimsOutpoints = createSelector(
|
||||
selectMyClaims,
|
||||
myClaims => {
|
||||
|
|
Loading…
Reference in a new issue