diff --git a/lbry b/lbry index e8bccec71..043e2d0ab 160000 --- a/lbry +++ b/lbry @@ -1 +1 @@ -Subproject commit e8bccec71c7424bf06d057904e4722d2d734fa3f +Subproject commit 043e2d0ab96030468d53d02e311fd848f35c2dc1 diff --git a/lbryum b/lbryum index 39ace3737..121bda396 160000 --- a/lbryum +++ b/lbryum @@ -1 +1 @@ -Subproject commit 39ace3737509ff2b09fabaaa64d1525843de1325 +Subproject commit 121bda3963ee94f0c9c027813c55b71b38219739 diff --git a/ui/js/app.js b/ui/js/app.js index 6126901e7..c8f585fb7 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -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 : diff --git a/ui/js/component/auth.js b/ui/js/component/auth.js index a42d3cc06..5b3d4d46e 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth.js @@ -40,7 +40,7 @@ const SubmitEmailStage = React.createClass({ return (
- { this._emailRow = ref }} type="text" label="Email" placeholder="webmaster@toplbryfan.com" + { this._emailRow = ref }} type="text" label="Email" placeholder="admin@toplbryfan.com" name="email" value={this.state.email} onChange={this.handleEmailChanged} />
@@ -125,9 +125,9 @@ const WelcomeStage = React.createClass({

Welcome to LBRY.

-

LBRY is kind of like a centaur. Totally normal up top, and way different underneath.

+

Using LBRY is like dating a centaur. Totally normal up top, and way different underneath.

On the upper level, LBRY is like other popular video and media sites.

-

Below, LBRY is like nothing else. Using blockchain and decentralization, LBRY is controlled by its users -- that is, you -- and no one else.

+

Below, LBRY is like nothing else. Using blockchain and decentralization, LBRY is controlled by its users -- you -- and no one else.

Thanks for being a part of it! Here's a nickel, kid.

@@ -180,7 +180,7 @@ export const AuthOverlay = React.createClass({ }, getInitialState: function() { return { - stage: "pending", + stage: null, stageProps: {} }; }, diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 98dbd45eb..c4e1324f1 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -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 ( - {formattedAmount} {parseFloat(formattedAmount) == 1.0 ? 'credit' : 'credits'} - { this.props.isEstimate ? * : null } + + {formattedAmount} + {this.props.label ? + (parseFloat(formattedAmount) == 1.0 ? ' credit' : ' credits') : '' } + + { this.props.isEstimate ? * : null } ); } }); +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 free; + } else { + if (this.props.metadata.fee.currency === "LBC") { + return + } else if (this.props.metadata.fee.currency === "USD") { + return ???; + } + } + } + return ( + this.state.cost !== null ? + : + ??? + ); + } +}); + var addressStyle = { fontFamily: '"Consolas", "Lucida Console", "Adobe Source Code Pro", monospace', }; diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index 410d14d66..9b81fa28c 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -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({ : '' } + + Confirm you want to purchase this bro. + 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({ diff --git a/ui/js/component/file-tile.js b/ui/js/component/file-tile.js index 199f341bd..f9522f1c6 100644 --- a/ui/js/component/file-tile.js +++ b/ui/js/component/file-tile.js @@ -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 ? - : - ... - ); - } -}); - /*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 (
@@ -242,7 +199,7 @@ export let FileCardStream = React.createClass({
{title}
- { !this.props.hidePrice ? : null} + { !this.props.hidePrice ? : null}
@@ -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, }); } }); diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 6397ca9f5..ef6f6a67c 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -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) => { diff --git a/ui/js/lighthouse.js b/ui/js/lighthouse.js index a8b60f0fa..41d2d996c 100644 --- a/ui/js/lighthouse.js +++ b/ui/js/lighthouse.js @@ -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({}, { diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 2709f3a8e..524201b97 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -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} />
- +
: null} diff --git a/ui/js/page/show.js b/ui/js/page/show.js index 7197390d2..eb9f02e46 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -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 ( -
-
- -
-
-

{description}

-
- - - - - - - - - - - - - - - - - - -
Content-Type{this.props.contentType}
Cost
Author{author}
Language{language}
License{license}
-
- -
- -
-
-
- ); + + + + + + + + + + + + + + + +
Content-Type{this.props.contentType}
Author{author}
Language{language}
License{license}
+ ); } }); -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 ( -
-

Sorry, no results found for "{name}".

-
); - } - - return ( -
- { this.props.metadata.thumbnail ?
: '' } -

{this.props.metadata.title}

-
{this.props.uri}
- {/* In future, anticipate multiple formats, just a guess at what it could look like - // var formats = this.props.metadata.formats - // return ({formats.map(function(format,i){ */} - - {/* })}); */} -
); - } -}); - -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; } + //
+ + const + metadata = this.state.uriLookupComplete ? this.state.metadata : null, + title = this.state.uriLookupComplete ? metadata.title : this._uri; + return ( -
- {this.state.uriLookupComplete ? ( - - ) : ( -
-

No content

- There is no content available at {this._uri}. If you reached this page from a link within the LBRY interface, please . Thanks! +
+
+ { this.props.contentType && this.props.contentType.startsWith('video/') ? +
+
+
+
+ +

{title}

+ { this.state.uriLookupComplete ? +
+
+ +
+
+ +
+
: '' }
- )} -
); + { this.state.uriLookupComplete ? +
+
+ {metadata.description} +
+
+ : } +
+
+ +
+
+ +
+
+ + ); } }); diff --git a/ui/js/page/watch.js b/ui/js/page/watch.js index ac270d77a..45341ded1 100644 --- a/ui/js/page/watch.js +++ b/ui/js/page/watch.js @@ -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 ( +
{ + !this.state.readyToPlay || true ? + 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 : + + }
+ ); + } +}) var WatchPage = React.createClass({ _isMounted: false, diff --git a/ui/js/rewards.js b/ui/js/rewards.js index a42ed1637..ec043de02 100644 --- a/ui/js/rewards.js +++ b/ui/js/rewards.js @@ -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); }); }); } diff --git a/ui/js/utils.js b/ui/js/utils.js index e9472a6a4..b24eb25b6 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -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); } /** diff --git a/ui/scss/_canvas.scss b/ui/scss/_canvas.scss index ac5712240..25eb836fc 100644 --- a/ui/scss/_canvas.scss +++ b/ui/scss/_canvas.scss @@ -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; + } } } diff --git a/ui/scss/_global.scss b/ui/scss/_global.scss index b203e7d18..68533b206 100644 --- a/ui/scss/_global.scss +++ b/ui/scss/_global.scss @@ -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); diff --git a/ui/scss/all.scss b/ui/scss/all.scss index dcb81cb98..b4c6611a6 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -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"; diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index 163de32d4..e019d7342 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -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; } diff --git a/ui/scss/component/_video.scss b/ui/scss/component/_video.scss new file mode 100644 index 000000000..c6800088c --- /dev/null +++ b/ui/scss/component/_video.scss @@ -0,0 +1,12 @@ +video { + border: 1px solid red; + object-fill: contain; +} + +.video-embedded { + max-width: 100%; + height: 0; + padding-bottom: 63%; + video { + } +} \ No newline at end of file diff --git a/ui/scss/page/_show.scss b/ui/scss/page/_show.scss new file mode 100644 index 000000000..c0434ee24 --- /dev/null +++ b/ui/scss/page/_show.scss @@ -0,0 +1,6 @@ +@import "../global"; + +.show-page-media { + text-align: center; + margin-bottom: $spacing-vertical; +} \ No newline at end of file