Seed Support #56

Closed
ocnios wants to merge 173 commits from master into build
20 changed files with 410 additions and 225 deletions
Showing only changes of commit c8c97e97ca - Show all commits

2
lbry

@ -1 +1 @@
Subproject commit e8bccec71c7424bf06d057904e4722d2d734fa3f
Subproject commit 043e2d0ab96030468d53d02e311fd848f35c2dc1

2
lbryum

@ -1 +1 @@
Subproject commit 39ace3737509ff2b09fabaaa64d1525843de1325
Subproject commit 121bda3963ee94f0c9c027813c55b71b38219739

View file

@ -287,7 +287,7 @@ var App = React.createClass({
var mainContent = this.getMainContent(),
headerLinks = this.getHeaderLinks(),
searchQuery = this.state.viewingPage == 'discover' && this.state.pageArgs ? this.state.pageArgs : '';
return (
this._fullScreenPages.includes(this.state.viewingPage) ?
mainContent :

View file

@ -40,7 +40,7 @@ const SubmitEmailStage = React.createClass({
return (
<section>
<form onSubmit={this.handleSubmit}>
<FormRow ref={(ref) => { this._emailRow = ref }} type="text" label="Email" placeholder="webmaster@toplbryfan.com"
<FormRow ref={(ref) => { this._emailRow = ref }} type="text" label="Email" placeholder="admin@toplbryfan.com"
name="email" value={this.state.email}
onChange={this.handleEmailChanged} />
<div className="form-row-submit">
@ -125,9 +125,9 @@ const WelcomeStage = React.createClass({
<Modal type="custom" isOpen={true} contentLabel="Welcome to LBRY" {...this.props}>
<section>
<h3 className="modal__header">Welcome to LBRY.</h3>
<p>LBRY is kind of like a centaur. Totally normal up top, and <em>way different</em> underneath.</p>
<p>Using LBRY is like dating a centaur. Totally normal up top, and <em>way different</em> underneath.</p>
<p>On the upper level, LBRY is like other popular video and media sites.</p>
<p>Below, LBRY is like nothing else. Using blockchain and decentralization, LBRY is controlled by its users -- that is, you -- and no one else.</p>
<p>Below, LBRY is like nothing else. Using blockchain and decentralization, LBRY is controlled by its users -- you -- and no one else.</p>
<p>Thanks for being a part of it! Here's a nickel, kid.</p>
<div style={{textAlign: "center", marginBottom: "12px"}}>
<RewardLink type="new_user" button="primary" onRewardClaim={this.onRewardClaim} onRewardFailure={this.props.endAuth} />
@ -180,7 +180,7 @@ export const AuthOverlay = React.createClass({
},
getInitialState: function() {
return {
stage: "pending",
stage: null,
stageProps: {}
};
},

View file

@ -60,20 +60,84 @@ export let CurrencySymbol = React.createClass({
export let CreditAmount = React.createClass({
propTypes: {
amount: React.PropTypes.number,
precision: React.PropTypes.number
amount: React.PropTypes.number.isRequired,
precision: React.PropTypes.number,
label: React.PropTypes.bool
},
getDefaultProps: function() {
return {
precision: 1,
label: true,
}
},
render: function() {
var formattedAmount = lbry.formatCredits(this.props.amount, this.props.precision ? this.props.precision : 1);
var formattedAmount = lbry.formatCredits(this.props.amount, this.props.precision);
return (
<span className="credit-amount">
<span>{formattedAmount} {parseFloat(formattedAmount) == 1.0 ? 'credit' : 'credits'}</span>
{ this.props.isEstimate ? <span style={estimateStyle}>*</span> : null }
<span>
{formattedAmount}
{this.props.label ?
(parseFloat(formattedAmount) == 1.0 ? ' credit' : ' credits') : '' }
</span>
{ this.props.isEstimate ? <span className="credit-amount__estimate" title="This is an estimate and does not include data fees">*</span> : null }
</span>
);
}
});
export let FilePrice = React.createClass({
_isMounted: false,
propTypes: {
metadata: React.PropTypes.object,
uri: React.PropTypes.string.isRequired,
},
getInitialState: function() {
return {
cost: null,
isEstimate: null,
}
},
componentDidMount: function() {
this._isMounted = true;
lbry.getCostInfo(this.props.uri).then(({cost, includesData}) => {
if (this._isMounted) {
this.setState({
cost: cost,
isEstimate: includesData,
});
}
}, (err) => {
// If we get an error looking up cost information, do nothing
});
},
componentWillUnmount: function() {
this._isMounted = false;
},
render: function() {
if (this.state.cost === null && this.props.metadata) {
if (!this.props.metadata.fee) {
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 (
this.state.cost !== null ?
<CreditAmount amount={this.state.cost} label={false} isEstimate={!this.state.isEstimate}/> :
<span className="credit-amount">???</span>
);
}
});
var addressStyle = {
fontFamily: '"Consolas", "Lucida Console", "Adobe Source Code Pro", monospace',
};

View file

@ -2,7 +2,7 @@ import React from 'react';
import lbry from '../lbry.js';
import {Link} from '../component/link.js';
import {Icon} from '../component/common.js';
import Modal from './modal.js';
import {Modal} from './modal.js';
import {FormField} from './form.js';
import {ToolTip} from '../component/tooltip.js';
import {DropDownMenu, DropDownMenuItem} from './menu.js';
@ -25,7 +25,7 @@ let WatchLink = React.createClass({
if (this.props.downloadStarted) {
this.startVideo();
} else {
lbry.getCostInfo(this.props.uri, ({cost}) => {
lbry.getCostInfo(this.props.uri).then(({cost}) => {
lbry.getBalance((balance) => {
if (cost > balance) {
this.setState({
@ -79,7 +79,8 @@ let FileActionsRow = React.createClass({
menuOpen: false,
deleteChecked: false,
attemptingDownload: false,
attemptingRemove: false
attemptingRemove: false,
affirmedPurchase: false
}
},
onFileInfoUpdate: function(fileInfo) {
@ -95,14 +96,16 @@ let FileActionsRow = React.createClass({
attemptingDownload: true,
attemptingRemove: false
});
lbry.getCostInfo(this.props.uri, ({cost}) => {
lbry.getCostInfo(this.props.uri).then(({cost}) => {
console.log(cost);
console.log(this.props.uri);
lbry.getBalance((balance) => {
if (cost > balance) {
this.setState({
modal: 'notEnoughCredits',
attemptingDownload: false,
});
} else {
} else if (this.state.affirmedPurchase) {
lbry.get({uri: this.props.uri}).then((streamInfo) => {
if (streamInfo === null || typeof streamInfo !== 'object') {
this.setState({
@ -111,6 +114,11 @@ let FileActionsRow = React.createClass({
});
}
});
} else {
this.setState({
attemptingDownload: false,
modal: 'affirmPurchase'
})
}
});
});
@ -153,6 +161,13 @@ let FileActionsRow = React.createClass({
attemptingDownload: false
});
},
onAffirmPurchase: function() {
this.setState({
affirmedPurchase: true,
modal: null
});
this.tryDownload();
},
openMenu: function() {
this.setState({
menuOpen: !this.state.menuOpen,
@ -209,6 +224,10 @@ let FileActionsRow = React.createClass({
<DropDownMenuItem key={0} onClick={this.handleRevealClicked} label={openInFolderMessage} />
<DropDownMenuItem key={1} onClick={this.handleRemoveClicked} label="Remove..." />
</DropDownMenu> : '' }
<Modal type="confirm" isOpen={this.state.modal == 'affirmPurchase'}
contentLabel="Confirm Purchase" onConfirmed={this.onAffirmPurchase} onAborted={this.closeModal}>
Confirm you want to purchase this bro.
</Modal>
<Modal isOpen={this.state.modal == 'notEnoughCredits'} contentLabel="Not enough credits"
onConfirmed={this.closeModal}>
You don't have enough LBRY credits to pay for this stream.
@ -261,6 +280,7 @@ export let FileActions = React.createClass({
componentDidMount: function() {
this._isMounted = true;
this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate);
lbry.get_availability({uri: this.props.uri}, (availability) => {
if (this._isMounted) {
this.setState({

View file

@ -3,52 +3,9 @@ import lbry from '../lbry.js';
import uri from '../uri.js';
import {Link} from '../component/link.js';
import {FileActions} from '../component/file-actions.js';
import {Thumbnail, TruncatedText, CreditAmount} from '../component/common.js';
import {Thumbnail, TruncatedText, FilePrice} from '../component/common.js';
import UriIndicator from '../component/channel-indicator.js';
let FilePrice = React.createClass({
_isMounted: false,
propTypes: {
uri: React.PropTypes.string
},
getInitialState: function() {
return {
cost: null,
costIncludesData: null,
}
},
componentDidMount: function() {
this._isMounted = true;
lbry.getCostInfo(this.props.uri, ({cost, includesData}) => {
if (this._isMounted) {
this.setState({
cost: cost,
costIncludesData: includesData,
});
}
}, (err) => {
console.log('error from getCostInfo callback:', err)
// If we get an error looking up cost information, do nothing
});
},
componentWillUnmount: function() {
this._isMounted = false;
},
render: function() {
return (
this.state.cost !== null ?
<CreditAmount amount={this.state.cost} isEstimate={!this.state.costIncludesData}/> :
<span className="credit-amount">...</span>
);
}
});
/*should be merged into FileTile once FileTile is refactored to take a single id*/
export let FileTileStream = React.createClass({
_fileInfoSubscribeId: null,
@ -234,7 +191,7 @@ export let FileCardStream = React.createClass({
const isConfirmed = typeof metadata == 'object';
const title = isConfirmed ? metadata.title : lbryUri;
const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw;
const primaryUrl = '?watch=' + lbryUri;
const primaryUrl = '?show=' + lbryUri;
return (
<section className={ 'card card--small card--link ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
<div className="card__inner">
@ -242,7 +199,7 @@ export let FileCardStream = React.createClass({
<div className="card__title-identity">
<h5><TruncatedText lines={1}>{title}</TruncatedText></h5>
<div className="card__subtitle">
{ !this.props.hidePrice ? <span style={{float: "right"}}><FilePrice uri={this.props.uri} /></span> : null}
{ !this.props.hidePrice ? <span style={{float: "right"}}><FilePrice uri={this.props.uri} metadata={metadata} /></span> : null}
<UriIndicator uri={lbryUri} metadata={metadata} contentType={this.props.contentType}
hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} />
</div>
@ -288,11 +245,12 @@ export let FileTile = React.createClass({
componentDidMount: function() {
this._isMounted = true;
lbry.resolve({uri: this.props.uri}).then(({claim: claimInfo}) => {
if (this._isMounted && claimInfo.value.stream.metadata) {
lbry.resolve({uri: this.props.uri}).then((resolutionInfo) => {
if (this._isMounted && resolutionInfo && resolutionInfo.claim && resolutionInfo.claim.value &&
resolutionInfo.claim.value.stream && resolutionInfo.claim.value.stream.metadata) {
// In case of a failed lookup, metadata will be null, in which case the component will never display
this.setState({
claimInfo: claimInfo,
claimInfo: resolutionInfo.claim,
});
}
});

View file

@ -1,7 +1,7 @@
import lighthouse from './lighthouse.js';
import jsonrpc from './jsonrpc.js';
import uri from './uri.js';
import {getLocal, setLocal} from './utils.js';
import {getLocal, getSession, setSession, setLocal} from './utils.js';
const {remote} = require('electron');
const menu = remote.require('./menu/main-menu');
@ -93,7 +93,6 @@ lbry.call = function (method, params, callback, errorCallback, connectFailedCall
jsonrpc.call(lbry.daemonConnectionString, method, [params], callback, errorCallback, connectFailedCallback);
}
//core
lbry._connectPromise = null;
lbry.connect = function() {
@ -171,16 +170,6 @@ lbry.sendToAddress = function(amount, address, callback, errorCallback) {
lbry.call("send_amount_to_address", { "amount" : amount, "address": address }, callback, errorCallback);
}
lbry.resolveName = function(name, callback) {
if (!name) {
throw new Error(`Name required.`);
}
lbry.call('resolve_name', { 'name': name }, callback, () => {
// For now, assume any error means the name was not resolved
callback(null);
});
}
lbry.getClaimInfo = function(name, callback) {
if (!name) {
throw new Error(`Name required.`);
@ -209,7 +198,7 @@ lbry.getPeersForBlobHash = function(blobHash, callback) {
});
}
lbry.getCostInfo = function(lbryUri, callback, errorCallback) {
lbry.getCostInfo = function(lbryUri) {
/**
* Takes a LBRY URI; will first try and calculate a total cost using
* Lighthouse. If Lighthouse can't be reached, it just retrives the
@ -223,24 +212,29 @@ lbry.getCostInfo = function(lbryUri, callback, errorCallback) {
if (!lbryUri) {
throw new Error(`URI required.`);
}
return new Promise((resolve, reject) => {
function getCost(lbryUri, size) {
lbry.stream_cost_estimate({uri: lbryUri, ... size !== null ? {size} : {}}).then((cost) => {
callback({
cost: cost,
includesData: size !== null,
});
}, reject);
}
function getCost(lbryUri, size, callback, errorCallback) {
lbry.stream_cost_estimate({uri: lbryUri, ... size !== null ? {size} : {}}).then((cost) => {
callback({
cost: cost,
includesData: size !== null,
});
}, errorCallback);
}
const uriObj = uri.parseLbryUri(lbryUri);
const name = uriObj.path || uriObj.name;
const uriObj = uri.parseLbryUri(lbryUri);
const name = uriObj.path || uriObj.name;
lighthouse.get_size_for_name(name).then((size) => {
getCost(name, size, callback, errorCallback);
}, () => {
getCost(name, null, callback, errorCallback);
});
lighthouse.get_size_for_name(name).then((size) => {
if (size) {
getCost(name, size);
} else {
getCost(name, null);
}
}, () => {
getCost(name, null);
});
})
}
lbry.getMyClaims = function(callback) {
@ -615,6 +609,25 @@ lbry.claim_list_mine = function(params={}) {
});
}
lbry.resolve = function(params={}) {
const claimCacheKey = 'resolve_claim_cache',
claimCache = getSession(claimCacheKey, {})
return new Promise((resolve, reject) => {
if (!params.uri) {
throw "Resolve has hacked cache on top of it that requires a URI"
}
if (params.uri && claimCache[params.uri]) {
resolve(claimCache[params.uri]);
} else {
lbry.call('resolve', params, function(data) {
claimCache[params.uri] = data;
setSession(claimCacheKey, claimCache)
resolve(data)
}, reject)
}
});
}
// lbry.get = function(params={}) {
// return function(params={}) {
// return new Promise((resolve, reject) => {

View file

@ -21,11 +21,7 @@ function getServers() {
function call(method, params, callback, errorCallback) {
if (connectTryNum > maxQueryTries) {
if (connectFailedCallback) {
connectFailedCallback();
} else {
throw new Error(`Could not connect to Lighthouse server. Last server attempted: ${server}`);
}
errorCallback(new Error(`Could not connect to Lighthouse server. Last server attempted: ${server}`));
}
/**
@ -48,7 +44,7 @@ function call(method, params, callback, errorCallback) {
}, () => {
connectTryNum++;
call(method, params, callback, errorCallback);
});
}, queryTimeout);
}
const lighthouse = new Proxy({}, {

View file

@ -4,6 +4,7 @@ import uri from '../uri.js';
import {FormField, FormRow} from '../component/form.js';
import {Link} from '../component/link.js';
import rewards from '../rewards.js';
import lbryio from '../lbryio.js';
import Modal from '../component/modal.js';
var PublishPage = React.createClass({
@ -26,6 +27,7 @@ var PublishPage = React.createClass({
// Calls API to update displayed list of channels. If a channel name is provided, will select
// that channel at the same time (used immediately after creating a channel)
lbry.channel_list_mine().then((channels) => {
rewards.claimReward(rewards.TYPE_FIRST_CHANNEL)
this.setState({
channels: channels,
... channel ? {channel} : {}
@ -348,7 +350,7 @@ var PublishPage = React.createClass({
},
componentWillMount: function() {
this._updateChannelList();
this._requestPublishReward();
// this._requestPublishReward();
},
componentDidMount: function() {
document.title = "Publish";
@ -510,7 +512,7 @@ var PublishPage = React.createClass({
onChange={this.handleNewChannelBidChange}
value={this.state.newChannelBid} />
<div className="form-row-submit">
<Link button="primary" label={!this.state.creatingChannel ? 'Creating identity' : 'Creating identity...'} onClick={this.handleCreateChannelClick} disabled={this.state.creatingChannel} />
<Link button="primary" label={!this.state.creatingChannel ? 'Create identity' : 'Creating identity...'} onClick={this.handleCreateChannelClick} disabled={this.state.creatingChannel} />
</div>
</div>
: null}

View file

@ -2,105 +2,45 @@ import React from 'react';
import lbry from '../lbry.js';
import lighthouse from '../lighthouse.js';
import uri from '../uri.js';
import {CreditAmount, Thumbnail} from '../component/common.js';
import {Video} from '../page/watch.js'
import {TruncatedText, Thumbnail, FilePrice, BusyMessage} from '../component/common.js';
import {FileActions} from '../component/file-actions.js';
import {Link} from '../component/link.js';
var formatItemImgStyle = {
maxWidth: '100%',
maxHeight: '100%',
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
marginTop: '5px',
};
import UriIndicator from '../component/channel-indicator.js';
var FormatItem = React.createClass({
propTypes: {
metadata: React.PropTypes.object,
contentType: React.PropTypes.string,
cost: React.PropTypes.number,
uri: React.PropTypes.string,
outpoint: React.PropTypes.string,
costIncludesData: React.PropTypes.bool,
},
render: function() {
const {thumbnail, author, title, description, language, license} = this.props.metadata;
const mediaType = lbry.getMediaType(this.props.contentType);
var costIncludesData = this.props.costIncludesData;
var cost = this.props.cost || 0.0;
return (
<div className="row-fluid">
<div className="span4">
<Thumbnail src={thumbnail} alt={'Photo for ' + title} style={formatItemImgStyle} />
</div>
<div className="span8">
<p>{description}</p>
<section>
<table className="table-standard">
<tbody>
<tr>
<td>Content-Type</td><td>{this.props.contentType}</td>
</tr>
<tr>
<td>Cost</td><td><CreditAmount amount={cost} isEstimate={!costIncludesData}/></td>
</tr>
<tr>
<td>Author</td><td>{author}</td>
</tr>
<tr>
<td>Language</td><td>{language}</td>
</tr>
<tr>
<td>License</td><td>{license}</td>
</tr>
</tbody>
</table>
</section>
<FileActions uri={this._uri} outpoint={this.props.outpoint} metadata={this.props.metadata} contentType={this.props.contentType} />
<section>
<Link href="https://lbry.io/dmca" label="report" className="button-text-help" />
</section>
</div>
</div>
);
<table className="table-standard">
<tbody>
<tr>
<td>Content-Type</td><td>{this.props.contentType}</td>
</tr>
<tr>
<td>Author</td><td>{author}</td>
</tr>
<tr>
<td>Language</td><td>{language}</td>
</tr>
<tr>
<td>License</td><td>{license}</td>
</tr>
</tbody>
</table>
);
}
});
var FormatsSection = React.createClass({
propTypes: {
uri: React.PropTypes.string,
outpoint: React.PropTypes.string,
metadata: React.PropTypes.object,
contentType: React.PropTypes.string,
cost: React.PropTypes.number,
costIncludesData: React.PropTypes.bool,
},
render: function() {
if(this.props.metadata == null)
{
return (
<div>
<h2>Sorry, no results found for "{name}".</h2>
</div>);
}
return (
<div>
{ this.props.metadata.thumbnail ? <div style={{backgroundImage: this.props.metadata.thumbnail}}></div> : '' }
<h1>{this.props.metadata.title}</h1>
<div className="meta">{this.props.uri}</div>
{/* In future, anticipate multiple formats, just a guess at what it could look like
// var formats = this.props.metadata.formats
// return (<tbody>{formats.map(function(format,i){ */}
<FormatItem metadata={this.props.metadata} contentType={this.props.contentType} cost={this.props.cost} uri={this.props.uri} outpoint={this.props.outpoint} costIncludesData={this.props.costIncludesData} />
{/* })}</tbody>); */}
</div>);
}
});
var ShowPage = React.createClass({
let ShowPage = React.createClass({
_uri: null,
propTypes: {
@ -110,6 +50,8 @@ var ShowPage = React.createClass({
return {
metadata: null,
contentType: null,
hasSignature: false,
signatureIsValid: false,
cost: null,
costIncludesData: null,
uriLookupComplete: null,
@ -119,16 +61,19 @@ var ShowPage = React.createClass({
this._uri = uri.normalizeLbryUri(this.props.uri);
document.title = this._uri;
lbry.resolve({uri: this._uri}).then(({txid, nout, claim: {value: {stream: {metadata, source: {contentType}}}}}) => {
lbry.resolve({uri: this._uri}).then(({ claim: {txid, nout, has_signature, signature_is_valid, value: {stream: {metadata, source: {contentType}}}}}) => {
console.log({txid, nout, claim: {value: {stream: {metadata, source: {contentType}}}}} );
this.setState({
outpoint: txid + ':' + nout,
metadata: metadata,
hasSignature: has_signature,
signatureIsValid: signature_is_valid,
contentType: contentType,
uriLookupComplete: true,
});
});
lbry.getCostInfo(this._uri, ({cost, includesData}) => {
lbry.getCostInfo(this._uri).then(({cost, includesData}) => {
this.setState({
cost: cost,
costIncludesData: includesData,
@ -140,17 +85,51 @@ var ShowPage = React.createClass({
return null;
}
// <div className="card__media" style={{ backgroundImage: "url('" + metadata.thumbnail + "')" }}></div>
const
metadata = this.state.uriLookupComplete ? this.state.metadata : null,
title = this.state.uriLookupComplete ? metadata.title : this._uri;
return (
<main>
{this.state.uriLookupComplete ? (
<FormatsSection uri={this._uri} outpoint={this.state.outpoint} metadata={this.state.metadata} cost={this.state.cost} costIncludesData={this.state.costIncludesData} contentType={this.state.contentType} />
) : (
<div>
<h2>No content</h2>
There is no content available at <strong>{this._uri}</strong>. If you reached this page from a link within the LBRY interface, please <Link href="?report" label="report a bug" />. Thanks!
<main className="constrained-page">
<section className="show-page-media">
{ this.props.contentType && this.props.contentType.startsWith('video/') ?
<Video className="video-embedded" uri={this._uri} /> :
<Thumbnail src={metadata.thumbnail} /> }
</section>
<section className="card">
<div className="card__inner">
<div className="card__title-identity">
<span style={{float: "right"}}><FilePrice uri={this._uri} /></span>
<h1>{title}</h1>
{ this.state.uriLookupComplete ?
<div>
<div className="card__subtitle">
<UriIndicator uri={this._uri} hasSignature={this.state.hasSignature} signatureIsValid={this.state.signatureIsValid} />
</div>
<div className="card__actions">
<FileActions uri={this._uri} outpoint={this.state.outpoint} metadata={this.state.metadata} contentType={this.state.contentType} />
</div>
</div> : '' }
</div>
)}
</main>);
{ this.state.uriLookupComplete ?
<div>
<div className="card__content card__subtext card__subtext card__subtext--allow-newlines">
{metadata.description}
</div>
</div>
: <BusyMessage message="Loading..." /> }
</div>
<div className="card__content">
<FormatItem metadata={metadata} contentType={this.state.contentType} cost={this.state.cost} uri={this._uri} outpoint={this.state.outpoint} costIncludesData={this.state.costIncludesData} />
</div>
<div className="card__content">
<Link href="https://lbry.io/dmca" label="report" className="button-text-help" />
</div>
</section>
</main>
);
}
});

View file

@ -7,6 +7,105 @@ import LoadScreen from '../component/load_screen.js'
const fs = require('fs');
const VideoStream = require('videostream');
export let Video = React.createClass({
_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
_controlsHideTimeout: null,
_outpoint: null,
propTypes: {
uri: React.PropTypes.string,
},
getInitialState: function() {
return {
downloadStarted: false,
readyToPlay: false,
loadStatusMessage: "Requesting stream",
mimeType: null,
controlsShown: false,
};
},
componentDidMount: function() {
lbry.get({uri: this.props.uri}).then((fileInfo) => {
this._outpoint = fileInfo.outpoint;
this.updateLoadStatus();
});
},
handleMouseMove: function() {
if (this._controlsTimeout) {
clearTimeout(this._controlsTimeout);
}
if (!this.state.controlsShown) {
this.setState({
controlsShown: true,
});
}
this._controlsTimeout = setTimeout(() => {
if (!this.isMounted) {
return;
}
this.setState({
controlsShown: false,
});
}, this._controlsHideDelay);
},
handleMouseLeave: function() {
if (this._controlsTimeout) {
clearTimeout(this._controlsTimeout);
}
if (this.state.controlsShown) {
this.setState({
controlsShown: false,
});
}
},
updateLoadStatus: function() {
lbry.file_list({
outpoint: this._outpoint,
full_status: true,
}).then(([status]) => {
if (!status || status.written_bytes == 0) {
// Download hasn't started yet, so update status message (if available) then try again
// TODO: Would be nice to check if we have the MOOV before starting playing
if (status) {
this.setState({
loadStatusMessage: status.message
});
}
setTimeout(() => { this.updateLoadStatus() }, 250);
} else {
this.setState({
readyToPlay: true,
mimeType: status.mime_type,
})
return
const mediaFile = {
createReadStream: function (opts) {
// Return a readable stream that provides the bytes
// between offsets "start" and "end" inclusive
console.log('Stream between ' + opts.start + ' and ' + opts.end + '.');
return fs.createReadStream(status.download_path, opts)
}
};
var elem = this.refs.video;
var videostream = VideoStream(mediaFile, elem);
elem.play();
}
});
},
render: function() {
return (
<div className={this.props.className}>{
!this.state.readyToPlay || true ?
<span>this is the world's world loading message and we shipped our software with it anyway... seriously it is actually loading... it might take a while though</span> :
<video className={this.props.className} id="video" ref="video"></video>
}</div>
);
}
})
var WatchPage = React.createClass({
_isMounted: false,

View file

@ -22,48 +22,67 @@ rewards.TYPE_NEW_DEVELOPER = "new_developer",
rewards.TYPE_FIRST_PUBLISH = "first_publish";
rewards.claimReward = function (type) {
function requestReward(resolve, reject, params) {
lbryio.call('reward', 'new', params, 'post').then(({RewardAmount}) => {
const
message = rewardMessage(type, RewardAmount),
result = {
type: type,
amount: RewardAmount,
message: message
};
// Display global notice
document.dispatchEvent(new CustomEvent('globalNotice', {
detail: {
message: message,
linkText: "Show All",
linkTarget: "?rewards",
isError: false,
},
}));
// Add more events here to display other places
resolve(result);
}, reject);
}
return new Promise((resolve, reject) => {
lbry.get_new_address().then((address) => {
const params = {
reward_type: type,
wallet_address: address,
};
switch (type) {
case 'first_channel':
//params.transaction_id = RelevantTransactionID;
case rewards.TYPE_FIRST_CHANNEL:
lbry.claim_list_mine().then(function(channels) {
if (channels.length) {
params.transaction_id = channels[0].txid;
requestReward(resolve, reject, params)
} else {
reject(new Error("Please create a channel identity first."))
}
}).catch(reject)
break;
case 'first_purchase':
//params.transaction_id = RelevantTransactionID;
// lbry.claim_list_mine().then(function(channels) {
// if (channels.length) {
// requestReward(resolve, reject, {transaction_id: channels[0].txid})
// }
// }).catch(reject)
break;
case 'first_channel':
//params.transaction_id = RelevantTransactionID;
break;
default:
requestReward(resolve, reject, params);
}
lbryio.call('reward', 'new', params, 'post').then(({RewardAmount}) => {
const
message = rewardMessage(type, RewardAmount),
result = {
type: type,
amount: RewardAmount,
message: message
};
// Display global notice
document.dispatchEvent(new CustomEvent('globalNotice', {
detail: {
message: message,
linkText: "Show All",
linkTarget: "?rewards",
isError: false,
},
}));
// Add more events here to display other places
resolve(result);
}, reject);
});
});
}

View file

@ -18,9 +18,9 @@ export function setLocal(key, value) {
* Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value
* is not set yet.
*/
export function getSession(key) {
export function getSession(key, fallback=undefined) {
const itemRaw = sessionStorage.getItem(key);
return itemRaw === null ? undefined : JSON.parse(itemRaw);
return itemRaw === null ? fallback : JSON.parse(itemRaw);
}
/**

View file

@ -62,6 +62,10 @@ $drawer-width: 220px;
font-weight: bold;
color: $color-money;
}
.credit-amount--estimate {
font-style: italic;
color: $color-meta-light;
}
#drawer-handle
{
padding: $spacing-vertical / 2;
@ -188,6 +192,12 @@ nav.sub-header
main
{
padding: $spacing-vertical;
&.constrained-page
{
max-width: $width-page-constrained;
margin-left: auto;
margin-right: auto;
}
}
}

View file

@ -31,6 +31,8 @@ $max-text-width: 660px;
$height-header: $spacing-vertical * 2.5;
$height-button: $spacing-vertical * 1.5;
$width-page-constrained: 800px;
$default-box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);
$focus-box-shadow: 2px 4px 4px 0 rgba(0,0,0,.14),2px 5px 3px -2px rgba(0,0,0,.2),2px 3px 7px 0 rgba(0,0,0,.12);

View file

@ -18,6 +18,8 @@
@import "component/_modal.scss";
@import "component/_modal-page.scss";
@import "component/_snack-bar.scss";
@import "component/_video.scss";
@import "page/_developer.scss";
@import "page/_watch.scss";
@import "page/_reward.scss";
@import "page/_show.scss";

View file

@ -5,7 +5,7 @@ $padding-card-horizontal: $spacing-vertical * 2/3;
.card {
margin-left: auto;
margin-right: auto;
max-width: 800px;
max-width: $width-page-constrained;
background: $color-bg;
box-shadow: $default-box-shadow;
border-radius: 2px;
@ -59,6 +59,9 @@ $padding-card-horizontal: $spacing-vertical * 2/3;
margin-bottom: $spacing-vertical * 2/3;
padding: 0 $padding-card-horizontal;
}
.card__subtext--allow-newlines {
white-space: pre-wrap;
}
.card__subtext--two-lines {
height: $font-size * 0.9 * $font-line-height * 2;
}
@ -118,7 +121,7 @@ $height-card-small: $spacing-vertical * 15;
{
margin-left: auto;
margin-right: auto;
max-width: 800px;
max-width: $width-page-constrained;
padding: $spacing-vertical / 2;
}

View file

@ -0,0 +1,12 @@
video {
border: 1px solid red;
object-fill: contain;
}
.video-embedded {
max-width: 100%;
height: 0;
padding-bottom: 63%;
video {
}
}

6
ui/scss/page/_show.scss Normal file
View file

@ -0,0 +1,6 @@
@import "../global";
.show-page-media {
text-align: center;
margin-bottom: $spacing-vertical;
}