Improvements to watch and download process #45
11 changed files with 160 additions and 78 deletions
|
@ -18,6 +18,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
||||||
* New publishes now display immediately in My Files, even before they hit the lbrynet file manager.
|
* New publishes now display immediately in My Files, even before they hit the lbrynet file manager.
|
||||||
* New welcome flow for new users
|
* New welcome flow for new users
|
||||||
* Redesigned UI for Discover
|
* Redesigned UI for Discover
|
||||||
|
* Handle more of price calculations at the daemon layer to improve page load time
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Update process now easier and more reliable
|
* Update process now easier and more reliable
|
||||||
|
|
2
lbry
2
lbry
|
@ -1 +1 @@
|
||||||
Subproject commit 043e2d0ab96030468d53d02e311fd848f35c2dc1
|
Subproject commit e8bccec71c7424bf06d057904e4722d2d734fa3f
|
2
lbryum
2
lbryum
|
@ -1 +1 @@
|
||||||
Subproject commit 121bda3963ee94f0c9c027813c55b71b38219739
|
Subproject commit 39ace3737509ff2b09fabaaa64d1525843de1325
|
|
@ -62,22 +62,34 @@ export let CreditAmount = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
amount: React.PropTypes.number.isRequired,
|
amount: React.PropTypes.number.isRequired,
|
||||||
precision: React.PropTypes.number,
|
precision: React.PropTypes.number,
|
||||||
label: React.PropTypes.bool
|
isEstimate: React.PropTypes.bool,
|
||||||
|
label: React.PropTypes.bool,
|
||||||
|
showFree: React.PropTypes.bool,
|
||||||
|
look: React.PropTypes.oneOf(['indicator', 'plain']),
|
||||||
},
|
},
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
precision: 1,
|
precision: 1,
|
||||||
label: true,
|
label: true,
|
||||||
|
showFree: false,
|
||||||
|
look: 'indicator',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
var formattedAmount = lbry.formatCredits(this.props.amount, this.props.precision);
|
const formattedAmount = lbry.formatCredits(this.props.amount, this.props.precision);
|
||||||
|
let amountText;
|
||||||
|
if (this.props.showFree && parseFloat(formattedAmount) == 0) {
|
||||||
|
amountText = 'free';
|
||||||
|
} else if (this.props.label) {
|
||||||
|
amountText = formattedAmount + (parseFloat(formattedAmount) == 1 ? ' credit' : ' credits');
|
||||||
|
} else {
|
||||||
|
amountText = formattedAmount;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="credit-amount">
|
<span className={`credit-amount credit-amount--${this.props.look}`}>
|
||||||
<span>
|
<span>
|
||||||
{formattedAmount}
|
{amountText}
|
||||||
{this.props.label ?
|
|
||||||
(parseFloat(formattedAmount) == 1.0 ? ' credit' : ' credits') : '' }
|
|
||||||
</span>
|
</span>
|
||||||
{ this.props.isEstimate ? <span className="credit-amount__estimate" title="This is an estimate and does not include data fees">*</span> : null }
|
{ this.props.isEstimate ? <span className="credit-amount__estimate" title="This is an estimate and does not include data fees">*</span> : null }
|
||||||
</span>
|
</span>
|
||||||
|
@ -89,15 +101,21 @@ export let FilePrice = React.createClass({
|
||||||
_isMounted: false,
|
_isMounted: false,
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
metadata: React.PropTypes.object,
|
|
||||||
uri: React.PropTypes.string.isRequired,
|
uri: React.PropTypes.string.isRequired,
|
||||||
|
look: React.PropTypes.oneOf(['indicator', 'plain']),
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
|
look: 'indicator',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this.setState({
|
||||||
cost: null,
|
cost: null,
|
||||||
isEstimate: null,
|
isEstimate: null,
|
||||||
}
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
@ -106,7 +124,7 @@ export let FilePrice = React.createClass({
|
||||||
if (this._isMounted) {
|
if (this._isMounted) {
|
||||||
this.setState({
|
this.setState({
|
||||||
cost: cost,
|
cost: cost,
|
||||||
isEstimate: includesData,
|
isEstimate: !includesData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
|
@ -119,22 +137,11 @@ export let FilePrice = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
if (this.state.cost === null && this.props.metadata) {
|
if (this.state.cost === null) {
|
||||||
if (!this.props.metadata.fee) {
|
return <span className={`credit-amount credit-amount--${this.props.look}`}>???</span>;
|
||||||
return <span className="credit-amount">free*</span>;
|
|
||||||
} else {
|
|
||||||
if (this.props.metadata.fee.currency === "LBC") {
|
|
||||||
return <CreditAmount label={false} amount={this.props.metadata.fee.amount} isEstimate={true} />
|
|
||||||
} else if (this.props.metadata.fee.currency === "USD") {
|
|
||||||
return <span className="credit-amount">???</span>;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
return <CreditAmount label={false} amount={this.state.cost} isEstimate={this.state.isEstimate} showFree={true} />
|
||||||
return (
|
|
||||||
this.state.cost !== null ?
|
|
||||||
<CreditAmount amount={this.state.cost} label={false} isEstimate={!this.state.isEstimate}/> :
|
|
||||||
<span className="credit-amount">???</span>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
|
import uri from '../uri.js';
|
||||||
import {Link} from '../component/link.js';
|
import {Link} from '../component/link.js';
|
||||||
import {Icon} from '../component/common.js';
|
import {Icon, FilePrice} from '../component/common.js';
|
||||||
import {Modal} from './modal.js';
|
import {Modal} from './modal.js';
|
||||||
import {FormField} from './form.js';
|
import {FormField} from './form.js';
|
||||||
import {ToolTip} from '../component/tooltip.js';
|
import {ToolTip} from '../component/tooltip.js';
|
||||||
|
@ -155,6 +156,8 @@ let FileActionsRow = React.createClass({
|
||||||
linkBlock = <Link label="Open" button="text" icon="icon-folder-open" onClick={this.onOpenClick} />;
|
linkBlock = <Link label="Open" button="text" icon="icon-folder-open" onClick={this.onOpenClick} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lbryUri = uri.normalizeLbryUri(this.props.uri);
|
||||||
|
const title = this.props.metadata ? this.props.metadata.title : lbryUri;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{this.state.fileInfo !== null || this.state.fileInfo.isMine
|
{this.state.fileInfo !== null || this.state.fileInfo.isMine
|
||||||
|
@ -167,7 +170,7 @@ let FileActionsRow = React.createClass({
|
||||||
</DropDownMenu> : '' }
|
</DropDownMenu> : '' }
|
||||||
<Modal type="confirm" isOpen={this.state.modal == 'affirmPurchase'}
|
<Modal type="confirm" isOpen={this.state.modal == 'affirmPurchase'}
|
||||||
contentLabel="Confirm Purchase" onConfirmed={this.onAffirmPurchase} onAborted={this.closeModal}>
|
contentLabel="Confirm Purchase" onConfirmed={this.onAffirmPurchase} onAborted={this.closeModal}>
|
||||||
Do you want to purchase this?
|
Are you sure you'd like to buy <strong>{title}</strong> for <strong><FilePrice uri={lbryUri} metadata={this.props.metadata} label={false} look="plain" /></strong> credits?
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal isOpen={this.state.modal == 'notEnoughCredits'} contentLabel="Not enough credits"
|
<Modal isOpen={this.state.modal == 'notEnoughCredits'} contentLabel="Not enough credits"
|
||||||
onConfirmed={this.closeModal}>
|
onConfirmed={this.closeModal}>
|
||||||
|
@ -175,12 +178,12 @@ let FileActionsRow = React.createClass({
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal isOpen={this.state.modal == 'timedOut'} contentLabel="Download failed"
|
<Modal isOpen={this.state.modal == 'timedOut'} contentLabel="Download failed"
|
||||||
onConfirmed={this.closeModal}>
|
onConfirmed={this.closeModal}>
|
||||||
LBRY was unable to download the stream <strong>lbry://{this.props.uri}</strong>.
|
LBRY was unable to download the stream <strong>{lbryUri}</strong>.
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal isOpen={this.state.modal == 'confirmRemove'} contentLabel="Not enough credits"
|
<Modal isOpen={this.state.modal == 'confirmRemove'} contentLabel="Not enough credits"
|
||||||
type="confirm" confirmButtonLabel="Remove" onConfirmed={this.handleRemoveConfirmed}
|
type="confirm" confirmButtonLabel="Remove" onConfirmed={this.handleRemoveConfirmed}
|
||||||
onAborted={this.closeModal}>
|
onAborted={this.closeModal}>
|
||||||
<p>Are you sure you'd like to remove <cite>{this.props.metadata ? this.props.metadata.title : this.props.uri}</cite> from LBRY?</p>
|
<p>Are you sure you'd like to remove <cite>{title}</cite> from LBRY?</p>
|
||||||
|
|
||||||
<label><FormField type="checkbox" checked={this.state.deleteChecked} onClick={this.handleDeleteCheckboxClicked} /> Delete this file from my computer</label>
|
<label><FormField type="checkbox" checked={this.state.deleteChecked} onClick={this.handleDeleteCheckboxClicked} /> Delete this file from my computer</label>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -26,7 +26,6 @@ export let FileTileStream = React.createClass({
|
||||||
return {
|
return {
|
||||||
showNsfwHelp: false,
|
showNsfwHelp: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
available: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -143,7 +142,6 @@ export let FileCardStream = React.createClass({
|
||||||
return {
|
return {
|
||||||
showNsfwHelp: false,
|
showNsfwHelp: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
available: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -232,7 +230,6 @@ export let FileTile = React.createClass({
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
uri: React.PropTypes.string.isRequired,
|
uri: React.PropTypes.string.isRequired,
|
||||||
available: React.PropTypes.bool,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import lbryio from './lbryio.js';
|
||||||
import lighthouse from './lighthouse.js';
|
import lighthouse from './lighthouse.js';
|
||||||
import jsonrpc from './jsonrpc.js';
|
import jsonrpc from './jsonrpc.js';
|
||||||
import uri from './uri.js';
|
import uri from './uri.js';
|
||||||
|
@ -216,12 +217,18 @@ lbry.getPeersForBlobHash = function(blobHash, callback) {
|
||||||
lbry.costPromiseCache = {}
|
lbry.costPromiseCache = {}
|
||||||
lbry.getCostInfo = function(lbryUri) {
|
lbry.getCostInfo = function(lbryUri) {
|
||||||
if (lbry.costPromiseCache[lbryUri] === undefined) {
|
if (lbry.costPromiseCache[lbryUri] === undefined) {
|
||||||
const COST_INFO_CACHE_KEY = 'cost_info_cache';
|
|
||||||
lbry.costPromiseCache[lbryUri] = new Promise((resolve, reject) => {
|
lbry.costPromiseCache[lbryUri] = new Promise((resolve, reject) => {
|
||||||
|
const COST_INFO_CACHE_KEY = 'cost_info_cache';
|
||||||
let costInfoCache = getSession(COST_INFO_CACHE_KEY, {})
|
let costInfoCache = getSession(COST_INFO_CACHE_KEY, {})
|
||||||
|
|
||||||
|
function cacheAndResolve(cost, includesData) {
|
||||||
|
costInfoCache[lbryUri] = {cost, includesData};
|
||||||
|
setSession(COST_INFO_CACHE_KEY, costInfoCache);
|
||||||
|
resolve({cost, includesData});
|
||||||
|
}
|
||||||
|
|
||||||
if (!lbryUri) {
|
if (!lbryUri) {
|
||||||
reject(new Error(`URI required.`));
|
return reject(new Error(`URI required.`));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (costInfoCache[lbryUri] && costInfoCache[lbryUri].cost) {
|
if (costInfoCache[lbryUri] && costInfoCache[lbryUri].cost) {
|
||||||
|
@ -230,18 +237,34 @@ lbry.getCostInfo = function(lbryUri) {
|
||||||
|
|
||||||
function getCost(lbryUri, size) {
|
function getCost(lbryUri, size) {
|
||||||
lbry.stream_cost_estimate({uri: lbryUri, ... size !== null ? {size} : {}}).then((cost) => {
|
lbry.stream_cost_estimate({uri: lbryUri, ... size !== null ? {size} : {}}).then((cost) => {
|
||||||
costInfoCache[lbryUri] = {
|
cacheAndResolve(cost, size !== null);
|
||||||
cost: cost,
|
|
||||||
includesData: size !== null,
|
|
||||||
};
|
|
||||||
setSession(COST_INFO_CACHE_KEY, costInfoCache);
|
|
||||||
resolve(costInfoCache[lbryUri]);
|
|
||||||
}, reject);
|
}, reject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCostGenerous(lbryUri) {
|
||||||
|
// If generous is on, the calculation is simple enough that we might as well do it here in the front end
|
||||||
|
lbry.resolve({uri: lbryUri}).then((resolutionInfo) => {
|
||||||
|
const fee = resolutionInfo.claim.value.stream.metadata.fee;
|
||||||
|
if (fee === undefined) {
|
||||||
|
cacheAndResolve(0, true);
|
||||||
|
} else if (fee.currency == 'LBC') {
|
||||||
|
cacheAndResolve(fee.amount, true);
|
||||||
|
} else {
|
||||||
|
lbryio.getExchangeRates().then(({lbc_usd}) => {
|
||||||
|
cacheAndResolve(fee.amount / lbc_usd, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const uriObj = uri.parseLbryUri(lbryUri);
|
const uriObj = uri.parseLbryUri(lbryUri);
|
||||||
const name = uriObj.path || uriObj.name;
|
const name = uriObj.path || uriObj.name;
|
||||||
|
|
||||||
|
lbry.settings_get({allow_cached: true}).then(({is_generous_host}) => {
|
||||||
|
if (is_generous_host) {
|
||||||
|
return getCostGenerous(lbryUri);
|
||||||
|
}
|
||||||
|
|
||||||
lighthouse.get_size_for_name(name).then((size) => {
|
lighthouse.get_size_for_name(name).then((size) => {
|
||||||
if (size) {
|
if (size) {
|
||||||
getCost(name, size);
|
getCost(name, size);
|
||||||
|
@ -253,6 +276,7 @@ lbry.getCostInfo = function(lbryUri) {
|
||||||
getCost(name, null);
|
getCost(name, null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return lbry.costPromiseCache[lbryUri];
|
return lbry.costPromiseCache[lbryUri];
|
||||||
}
|
}
|
||||||
|
@ -647,6 +671,23 @@ lbry.resolve = function(params={}) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds caching.
|
||||||
|
lbry.settings_get = function(params={}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (params.allow_cached) {
|
||||||
|
const cached = getSession('settings');
|
||||||
|
if (cached) {
|
||||||
|
return resolve(cached);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lbry.call('settings_get', {}, (settings) => {
|
||||||
|
setSession('settings', settings);
|
||||||
|
resolve(settings);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// lbry.get = function(params={}) {
|
// lbry.get = function(params={}) {
|
||||||
// return function(params={}) {
|
// return function(params={}) {
|
||||||
// return new Promise((resolve, reject) => {
|
// return new Promise((resolve, reject) => {
|
||||||
|
|
|
@ -10,7 +10,8 @@ const lbryio = {
|
||||||
enabled: false
|
enabled: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const CONNECTION_STRING = 'http://localhost:8080/';
|
const CONNECTION_STRING = 'https://api.lbry.io/';
|
||||||
|
const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000;
|
||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
'reward_type.get': ({name}) => {
|
'reward_type.get': ({name}) => {
|
||||||
|
@ -24,12 +25,32 @@ const mocks = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
lbryio.call = function(resource, action, params={}, method='get') {
|
|
||||||
|
lbryio.getExchangeRates = function() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!lbryio.enabled && (resource != 'discover' || action != 'list')) {
|
const cached = getSession('exchangeRateCache');
|
||||||
reject(new Error("LBRY internal API is disabled"))
|
if (!cached || Date.now() - cached.time > EXCHANGE_RATE_TIMEOUT) {
|
||||||
|
lbryio.call('lbc', 'exchange_rate', {}, 'get', true).then(({lbc_usd, lbc_btc, btc_usd}) => {
|
||||||
|
const rates = {lbc_usd, lbc_btc, btc_usd};
|
||||||
|
setSession('exchangeRateCache', {
|
||||||
|
rates: rates,
|
||||||
|
time: Date.now(),
|
||||||
|
});
|
||||||
|
resolve(rates);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve(cached.rates);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
lbryio.call = function(resource, action, params={}, method='get', evenIfDisabled=false) { // evenIfDisabled is just for development, when we may have some calls working and some not
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!lbryio.enabled && !evenIfDisabled && (resource != 'discover' || action != 'list')) {
|
||||||
|
reject(new Error("LBRY interal API is disabled"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/* temp code for mocks */
|
/* temp code for mocks */
|
||||||
if (`${resource}.${action}` in mocks) {
|
if (`${resource}.${action}` in mocks) {
|
||||||
resolve(mocks[`${resource}.${action}`](params));
|
resolve(mocks[`${resource}.${action}`](params));
|
||||||
|
|
|
@ -55,6 +55,7 @@ let ShowPage = React.createClass({
|
||||||
cost: null,
|
cost: null,
|
||||||
costIncludesData: null,
|
costIncludesData: null,
|
||||||
uriLookupComplete: null,
|
uriLookupComplete: null,
|
||||||
|
isDownloaded: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -62,8 +63,16 @@ let ShowPage = React.createClass({
|
||||||
document.title = this._uri;
|
document.title = this._uri;
|
||||||
|
|
||||||
lbry.resolve({uri: this._uri}).then(({ claim: {txid, nout, has_signature, signature_is_valid, value: {stream: {metadata, source: {contentType}}}}}) => {
|
lbry.resolve({uri: this._uri}).then(({ claim: {txid, nout, has_signature, signature_is_valid, value: {stream: {metadata, source: {contentType}}}}}) => {
|
||||||
|
const outpoint = txid + ':' + nout;
|
||||||
|
|
||||||
|
lbry.file_list({outpoint}).then((fileInfo) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
outpoint: txid + ':' + nout,
|
isDownloaded: fileInfo.length > 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
outpoint: outpoint,
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
hasSignature: has_signature,
|
hasSignature: has_signature,
|
||||||
signatureIsValid: signature_is_valid,
|
signatureIsValid: signature_is_valid,
|
||||||
|
@ -80,21 +89,21 @@ let ShowPage = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
const
|
const metadata = this.state.metadata;
|
||||||
metadata = this.state.uriLookupComplete ? this.state.metadata : null,
|
const title = metadata ? this.state.metadata.title : this._uri;
|
||||||
title = this.state.uriLookupComplete ? metadata.title : this._uri;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="constrained-page">
|
<main className="constrained-page">
|
||||||
<section className="show-page-media">
|
<section className="show-page-media">
|
||||||
{ this.state.contentType && this.state.contentType.startsWith('video/') ?
|
{ this.state.contentType && this.state.contentType.startsWith('video/') ?
|
||||||
<Video className="video-embedded" uri={this._uri} metadata={metadata} /> :
|
<Video className="video-embedded" uri={this._uri} metadata={metadata} outpoint={this.state.outpoint} /> :
|
||||||
(metadata ? <Thumbnail src={metadata.thumbnail} /> : <Thumbnail />) }
|
(metadata ? <Thumbnail src={metadata.thumbnail} /> : <Thumbnail />) }
|
||||||
</section>
|
</section>
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<div className="card__inner">
|
<div className="card__inner">
|
||||||
<div className="card__title-identity">
|
<div className="card__title-identity">
|
||||||
<span style={{float: "right"}}><FilePrice uri={this._uri} metadata={metadata} /></span>
|
{this.state.isDownloaded === false
|
||||||
|
? <span style={{float: "right"}}><FilePrice uri={this._uri} metadata={this.state.metadata} /></span>
|
||||||
|
: null}
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
{ this.state.uriLookupComplete ?
|
{ this.state.uriLookupComplete ?
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Icon, Thumbnail} from '../component/common.js';
|
import {Icon, Thumbnail, FilePrice} from '../component/common.js';
|
||||||
import {Link} from '../component/link.js';
|
import {Link} from '../component/link.js';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
import Modal from '../component/modal.js';
|
import Modal from '../component/modal.js';
|
||||||
|
@ -13,13 +13,14 @@ const VideoStream = require('videostream');
|
||||||
export let WatchLink = React.createClass({
|
export let WatchLink = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
uri: React.PropTypes.string,
|
uri: React.PropTypes.string,
|
||||||
|
metadata: React.PropTypes.object,
|
||||||
downloadStarted: React.PropTypes.bool,
|
downloadStarted: React.PropTypes.bool,
|
||||||
onGet: React.PropTypes.func
|
onGet: React.PropTypes.func,
|
||||||
},
|
},
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
affirmedPurchase: false
|
affirmedPurchase: false
|
||||||
},
|
},
|
||||||
onAffirmPurchase: function() {
|
play: function() {
|
||||||
lbry.get({uri: this.props.uri}).then((streamInfo) => {
|
lbry.get({uri: this.props.uri}).then((streamInfo) => {
|
||||||
if (streamInfo === null || typeof streamInfo !== 'object') {
|
if (streamInfo === null || typeof streamInfo !== 'object') {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -50,13 +51,19 @@ export let WatchLink = React.createClass({
|
||||||
attemptingDownload: false,
|
attemptingDownload: false,
|
||||||
});
|
});
|
||||||
} else if (cost <= 0.01) {
|
} else if (cost <= 0.01) {
|
||||||
this.onAffirmPurchase()
|
this.play()
|
||||||
|
} else {
|
||||||
|
lbry.file_list({outpoint: this.props.outpoint}).then((fileInfo) => {
|
||||||
|
if (fileInfo) { // Already downloaded
|
||||||
|
this.play();
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
modal: 'affirmPurchase'
|
modal: 'affirmPurchase'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -83,8 +90,8 @@ export let WatchLink = React.createClass({
|
||||||
You don't have enough LBRY credits to pay for this stream.
|
You don't have enough LBRY credits to pay for this stream.
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal type="confirm" isOpen={this.state.modal == 'affirmPurchase'}
|
<Modal type="confirm" isOpen={this.state.modal == 'affirmPurchase'}
|
||||||
contentLabel="Confirm Purchase" onConfirmed={this.onAffirmPurchase} onAborted={this.closeModal}>
|
contentLabel="Confirm Purchase" onConfirmed={this.play} onAborted={this.closeModal}>
|
||||||
Do you want to purchase this?
|
Are you sure you'd like to buy <strong>{this.props.metadata.title}</strong> for <strong><FilePrice uri={this.props.uri} metadata={this.props.metadata} label={false} look="plain" /></strong> credits?
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
@ -95,10 +102,11 @@ export let Video = React.createClass({
|
||||||
_isMounted: false,
|
_isMounted: false,
|
||||||
_controlsHideDelay: 3000, // Note: this needs to be shorter than the built-in delay in Electron, or Electron will hide the controls before us
|
_controlsHideDelay: 3000, // Note: this needs to be shorter than the built-in delay in Electron, or Electron will hide the controls before us
|
||||||
_controlsHideTimeout: null,
|
_controlsHideTimeout: null,
|
||||||
_outpoint: null,
|
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
uri: React.PropTypes.string,
|
uri: React.PropTypes.string.isRequired,
|
||||||
|
metadata: React.PropTypes.object,
|
||||||
|
outpoint: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
|
@ -113,7 +121,6 @@ export let Video = React.createClass({
|
||||||
},
|
},
|
||||||
onGet: function() {
|
onGet: function() {
|
||||||
lbry.get({uri: this.props.uri}).then((fileInfo) => {
|
lbry.get({uri: this.props.uri}).then((fileInfo) => {
|
||||||
this._outpoint = fileInfo.outpoint;
|
|
||||||
this.updateLoadStatus();
|
this.updateLoadStatus();
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -158,7 +165,7 @@ export let Video = React.createClass({
|
||||||
},
|
},
|
||||||
updateLoadStatus: function() {
|
updateLoadStatus: function() {
|
||||||
lbry.file_list({
|
lbry.file_list({
|
||||||
outpoint: this._outpoint,
|
outpoint: this.props.outpoint,
|
||||||
full_status: true,
|
full_status: true,
|
||||||
}).then(([status]) => {
|
}).then(([status]) => {
|
||||||
if (!status || status.written_bytes == 0) {
|
if (!status || status.written_bytes == 0) {
|
||||||
|
@ -200,7 +207,7 @@ export let Video = React.createClass({
|
||||||
<span>this is the world's world loading screen and we shipped our software with it anyway... <br/><br/>{this.state.loadStatusMessage}</span> :
|
<span>this is the world's world loading screen and we shipped our software with it anyway... <br/><br/>{this.state.loadStatusMessage}</span> :
|
||||||
<video controls id="video" ref="video"></video> :
|
<video controls id="video" ref="video"></video> :
|
||||||
<div className="video__cover" style={{backgroundImage: 'url("' + this.props.metadata.thumbnail + '")'}}>
|
<div className="video__cover" style={{backgroundImage: 'url("' + this.props.metadata.thumbnail + '")'}}>
|
||||||
<WatchLink className="video__play-button" uri={this.props.uri} onGet={this.onGet} icon="icon-play"></WatchLink>
|
<WatchLink className="video__play-button" uri={this.props.uri} metadata={this.props.metadata} outpoint={this.props.outpoint} onGet={this.onGet} icon="icon-play"></WatchLink>
|
||||||
</div>
|
</div>
|
||||||
}</div>
|
}</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -57,15 +57,11 @@ $drawer-width: 220px;
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
.credit-amount
|
.credit-amount--indicator
|
||||||
{
|
{
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $color-money;
|
color: $color-money;
|
||||||
}
|
}
|
||||||
.credit-amount--estimate {
|
|
||||||
font-style: italic;
|
|
||||||
color: $color-meta-light;
|
|
||||||
}
|
|
||||||
#drawer-handle
|
#drawer-handle
|
||||||
{
|
{
|
||||||
padding: $spacing-vertical / 2;
|
padding: $spacing-vertical / 2;
|
||||||
|
|
Loading…
Reference in a new issue