Merge branch 'revoke_claims'

This commit is contained in:
Jeremy Kauffman 2017-11-08 18:15:15 -05:00
commit 8987fca22c
15 changed files with 237 additions and 34 deletions

View file

@ -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)

View file

@ -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);
};
}

View file

@ -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));
}
}

View file

@ -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);

View file

@ -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 &&

View file

@ -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>

View file

@ -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!");
}

View file

@ -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";

View file

@ -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";

View file

@ -0,0 +1 @@
export const TIP = "tip";

View 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);

View 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;

View file

@ -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;
}

View file

@ -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,
});

View file

@ -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 => {