diff --git a/CHANGELOG.md b/CHANGELOG.md index 10c3f05b7..01532c8b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * Added a loading message to file actions * URL is auto suggested in Publish Page * Added infinite scroll to channel pages + * Added claim support button and inline form to file page. ### Changed * Publishing revamped. Editing claims is much easier. diff --git a/ui/js/actions/claims.js b/ui/js/actions/claims.js new file mode 100644 index 000000000..8ac1d7548 --- /dev/null +++ b/ui/js/actions/claims.js @@ -0,0 +1,68 @@ +import * as types from "constants/action_types"; +import lbry from "lbry"; +import { selectClaimSupport, selectClaimSupportAmount } from "selectors/claims"; +import { selectBalance } from "selectors/wallet"; +import { doOpenModal } from "actions/app"; + +export function doClaimNewSupport() { + return function(dispatch, getState) { + const state = getState(); + const claimSupport = selectClaimSupport(state); + const balance = selectBalance(state); + const amount = selectClaimSupportAmount(state); + + if (balance - amount < 1) { + return dispatch(doOpenModal("insufficientBalance")); + } + + dispatch({ + type: types.CLAIM_SUPPORT_STARTED, + }); + + const successCallback = results => { + // txid hash present indicates successful request + if (results.txid && results.txid.length > 0) { + dispatch({ + type: types.CLAIM_SUPPORT_COMPLETED, + }); + dispatch(doOpenModal("transactionSuccessful")); + } else { + dispatch({ + type: types.CLAIM_SUPPORT_FAILED, + data: { error: results }, + }); + dispatch(doOpenModal("transactionFailed")); + } + }; + + const errorCallback = error => { + dispatch({ + type: types.CLAIM_SUPPORT_FAILED, + data: { error: error.message }, + }); + dispatch(doOpenModal("transactionFailed")); + }; + + lbry + .claim_new_support({ + name: claimSupport.name, + claim_id: claimSupport.claim_id, + amount: claimSupport.amount, + }) + .then(successCallback, errorCallback); + }; +} + +export function doSetClaimSupportAmount(amount) { + return { + type: types.SET_CLAIM_SUPPORT_AMOUNT, + data: { amount }, + }; +} + +export function doSetClaimSupportClaim(claim_id, name) { + return { + type: types.SET_CLAIM_SUPPORT_CLAIM, + data: { claim_id, name }, + }; +} diff --git a/ui/js/component/fileActions/index.js b/ui/js/component/fileActions/index.js index 4311823d1..d1d2b9f9d 100644 --- a/ui/js/component/fileActions/index.js +++ b/ui/js/component/fileActions/index.js @@ -6,6 +6,7 @@ import { makeSelectDownloadingForUri, makeSelectLoadingForUri, } from "selectors/file_info"; + import { makeSelectIsAvailableForUri } from "selectors/availability"; import { selectCurrentModal } from "selectors/app"; import { makeSelectCostInfoForUri } from "selectors/cost_info"; @@ -15,8 +16,16 @@ import { doOpenFileInShell, doOpenFileInFolder } from "actions/file_info"; import { makeSelectClaimForUriIsMine } from "selectors/claims"; import { doPurchaseUri, doLoadVideo, doStartDownload } from "actions/content"; import FileActions from "./view"; +import { makeSelectClaimForUri } from "selectors/claims"; +import { + doClaimNewSupport, + doSetClaimSupportAmount, + doSetClaimSupportClaim, +} from "actions/claims"; +import { selectClaimSupportAmount } from "selectors/claims"; const makeSelect = () => { + const selectClaim = makeSelectClaimForUri(); const selectFileInfoForUri = makeSelectFileInfoForUri(); const selectIsAvailableForUri = makeSelectIsAvailableForUri(); const selectDownloadingForUri = makeSelectDownloadingForUri(); @@ -25,6 +34,7 @@ const makeSelect = () => { const selectClaimForUriIsMine = makeSelectClaimForUriIsMine(); const select = (state, props) => ({ + claim: selectClaim(state, props), fileInfo: selectFileInfoForUri(state, props), /*availability check is disabled due to poor performance, TBD if it dies forever or requires daemon fix*/ isAvailable: true, //selectIsAvailableForUri(state, props), @@ -34,6 +44,7 @@ const makeSelect = () => { costInfo: selectCostInfoForUri(state, props), loading: selectLoadingForUri(state, props), claimIsMine: selectClaimForUriIsMine(state, props), + amount: selectClaimSupportAmount(state), }); return select; @@ -48,6 +59,10 @@ const perform = dispatch => ({ startDownload: uri => dispatch(doPurchaseUri(uri, "affirmPurchase")), loadVideo: uri => dispatch(doLoadVideo(uri)), restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)), + claimNewSupport: () => dispatch(doClaimNewSupport()), + setAmount: event => dispatch(doSetClaimSupportAmount(event.target.value)), + setClaimSupport: (claim_id, name) => + dispatch(doSetClaimSupportClaim(claim_id, name)), }); export default connect(makeSelect, perform)(FileActions); diff --git a/ui/js/component/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index df624a345..344e5fe48 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -1,6 +1,7 @@ import React from "react"; import { Icon, BusyMessage } from "component/common"; import FilePrice from "component/filePrice"; +import { FormField } from "component/form"; import { Modal } from "component/modal"; import Link from "component/link"; import { ToolTip } from "component/tooltip"; @@ -57,6 +58,58 @@ class FileActions extends React.PureComponent { this.props.loadVideo(this.props.uri); } + onSupportClaimClicked(event) { + if (this.state.showSupportClaimForm) { + return; + } + + let button; + let parentCard; + if ("a" === event.target.tagName.toLowerCase()) { + button = event.target; + } + + let parent = event.target.parentElement; + do { + if (!button && "a" === parent.tagName.toLowerCase()) { + button = parent; + } + + if ("card" === parent.className.trim()) { + parentCard = parent; + } + parent = parent.parentElement; + } while (parent && !parentCard); + + this.setState({ + showSupportClaimForm: true, + supportClaimLinkOffset: button && parentCard + ? button.getBoundingClientRect().left - + parentCard.getBoundingClientRect().left - + 12 /* left pad + icon */ + : 0, + }); + } + + sendSupportClaim() { + this.setState({ supportInProgress: true }); + const { claim, setClaimSupport, claimNewSupport } = this.props; + setClaimSupport(claim.claim_id, claim.name); + claimNewSupport(); + } + + onClaimSupportSuccessful() { + this.setState({ + showSupportClaimForm: false, + }); + this.onClaimSupportCompleted(); + } + + onClaimSupportCompleted() { + this.props.closeModal(); + this.setState({ supportInProgress: false }); + } + render() { const { fileInfo, @@ -73,6 +126,8 @@ class FileActions extends React.PureComponent { costInfo, loading, claimIsMine, + amount, + setAmount, } = this.props; const metadata = fileInfo ? fileInfo.metadata : null, @@ -180,6 +235,45 @@ class FileActions extends React.PureComponent { /> : ""} + + {this.state.showSupportClaimForm && +