From c5d4941535e15edb01ae20dc757cfbae59c3ecfa Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 28 Mar 2017 05:07:52 -0400 Subject: [PATCH 01/51] Basic views for reward and reward list pages --- ui/js/app.js | 18 ++++++++---- ui/js/page/reward.js | 46 +++++++++++++++++++++++++++++ ui/js/page/rewards.js | 68 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 ui/js/page/reward.js create mode 100644 ui/js/page/rewards.js diff --git a/ui/js/app.js b/ui/js/app.js index 7e8559be7..2677c0b91 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -9,6 +9,8 @@ import ReportPage from './page/report.js'; import StartPage from './page/start.js'; import ClaimCodePage from './page/claim_code.js'; import ReferralPage from './page/referral.js'; +import RewardsPage from './page/rewards.js'; +import RewardPage from './page/reward.js'; import WalletPage from './page/wallet.js'; import ShowPage from './page/show.js'; import PublishPage from './page/publish.js'; @@ -233,12 +235,14 @@ var App = React.createClass({ case 'receive': case 'claim': case 'referral': + case 'rewards': return { - '?wallet' : 'Overview', - '?send' : 'Send', - '?receive' : 'Receive', - '?claim' : 'Claim Beta Code', - '?referral' : 'Check Referral Credit', + '?wallet': 'Overview', + '?send': 'Send', + '?receive': 'Receive', + '?claim': 'Claim Beta Code', + '?referral': 'Check Referral Credit', + '?rewards': 'Rewards', }; case 'downloaded': case 'published': @@ -272,6 +276,10 @@ var App = React.createClass({ return ; case 'referral': return ; + case 'rewards': + return ; + case 'reward': + return ; case 'wallet': case 'send': case 'receive': diff --git a/ui/js/page/reward.js b/ui/js/page/reward.js new file mode 100644 index 000000000..9c613ef3c --- /dev/null +++ b/ui/js/page/reward.js @@ -0,0 +1,46 @@ +import React from 'react'; + +// Placeholder for something like api.lbry.io/reward_type/get/[name] */ +function apiRewardTypeGet(name) { + return { + name: 'reward1', + title: 'Reward 1', + description: 'Reward 1 description', + value: 50, + claimed: true, + }; +} + +const RewardPage = React.createClass({ + propTypes: { + name: React.PropTypes.string, + }, + getInitialState: function() { + return { + rewardType: null, + }; + }, + componentWillMount: function() { + this.setState({ + rewardType: apiRewardTypeGet(this.props.name), + }); + }, + render: function() { + if (!this.state.rewardType) { + return null; + } + + let {title, description, value} = this.state.rewardType; + return ( +
+
+

{title}

+

{description}

+ {/* Most likely have a component included here for each reward (e.g. WatchVideoReward) */} +
+
+ ); + } +}); + +export default RewardPage; diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js new file mode 100644 index 000000000..093821f31 --- /dev/null +++ b/ui/js/page/rewards.js @@ -0,0 +1,68 @@ +import React from 'react'; +import lbry from '../lbry.js'; +import Modal from '../component/modal.js'; +import {Link} from '../component/link.js'; + +// Placeholder for something like api.lbry.io/reward_type/list */ +function apiRewardTypeList() { + return [ + { + name: 'link_github', + title: 'Link your GitHub account', + description: 'Link LBRY to your GitHub account', + value: 50, + claimed: false, + }, + ]; +} + +var RewardTile = React.createClass({ + propTypes: { + name: React.PropTypes.string, + title: React.PropTypes.string, + }, + render: function() { + return ( +
+
+

+
{this.props.description}
+ {this.props.claimed + ? This reward has been claimed. + : } +
+
+ ); + } +}); + +var RewardsPage = React.createClass({ + componentWillMount: function() { + this.setState({ + rewardTypes: apiRewardTypeList(), + }); + }, + getInitialState: function() { + return { + rewardTypes: null, + }; + }, + render: function() { + return ( +
+
+
+

Rewards

+ {!this.state.rewardTypes + ? null + : this.state.rewardTypes.map(({name, title, description, claimed, value}) => { + return ; + })} +
+
+
+ ); + } +}); + +export default RewardsPage; From 6cc2892399959704b2564cd166319f2fb78a9f01 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 29 Mar 2017 18:44:18 -0400 Subject: [PATCH 02/51] More progress on Rewards * Add wrapper for lbry.io API * View and basic logic for GitHub reward (not working yet) --- ui/js/lbryio.js | 91 ++++++++++++++++++++++++++++++++++ ui/js/page/reward.js | 101 +++++++++++++++++++++++++++++++------- ui/js/page/rewards.js | 33 ++++++------- ui/scss/all.scss | 3 +- ui/scss/page/_reward.scss | 3 ++ 5 files changed, 194 insertions(+), 37 deletions(-) create mode 100644 ui/js/lbryio.js create mode 100644 ui/scss/page/_reward.scss diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js new file mode 100644 index 000000000..c65767aaa --- /dev/null +++ b/ui/js/lbryio.js @@ -0,0 +1,91 @@ +const querystring = require('querystring'); + +const lbryio = {}; + +const CONNECTION_STRING = 'https://api.lbry.io/'; + +const mocks = { + 'reward_type.get': (name) => { + return { + name: 'link_github', + title: 'Link your GitHub account', + description: 'Link LBRY to your GitHub account', + value: 50, + claimed: false, + }; + }, + 'reward_type.list': () => { + return [ + { + name: 'link_github', + title: 'Link your GitHub account', + description: 'Link LBRY to your GitHub account', + value: 50, + claimed: false, + }, + ]; + } +}; + +lbryio.call = function(resource, action, params, method='get') { + console.log('top of lbryio.call') + return new Promise((resolve, reject) => { + console.log('top of promise handler') + /* temp code for mocks */ + if (`${resource}.${action}` in mocks) { + console.log(`found ${resource}.${action} in mocks`) + resolve(mocks[`${resource}.${action}`](params)); + console.log('...resolved.'); + return; + } else { + console.log(`did not find ${resource}.${action} in mocks`); + } + /* end temp */ + + console.log('about to create xhr object'); + const xhr = new XMLHttpRequest; + xhr.addEventListener('error', function (error) { + console.log('received XHR error:', error); + reject(error); + }); + + + console.log('about to add timeout listener'); + xhr.addEventListener('timeout', function() { + console.log('XHR timed out'); + + reject(new Error('XMLHttpRequest connection timed out')); + }); + + console.log('about to create load listener'); + xhr.addEventListener('load', function() { + console.log('loaded'); + const response = JSON.parse(xhr.responseText); + + if (response.error) { + if (reject) { + reject(new Error(response.error)); + } else { + document.dispatchEvent(new CustomEvent('unhandledError', { + detail: { + connectionString: connectionString, + method: method, + params: params, + code: response.error.code, + message: response.error.message, + data: response.error.data, + } + })); + } + } else { + resolve(response.result); + } + }); + + console.log('about to call xhr.open'); + xhr.open(method, CONNECTION_STRING + resource + '/' + action, true); + xhr.send(querystring.stringify(params)); + }); +}; + +export default lbryio; diff --git a/ui/js/page/reward.js b/ui/js/page/reward.js index 9c613ef3c..8c148ad80 100644 --- a/ui/js/page/reward.js +++ b/ui/js/page/reward.js @@ -1,19 +1,80 @@ import React from 'react'; +import lbryio from '../lbryio.js'; +import {Link} from '../component/link.js'; +import {CreditAmount} from '../component/common.js'; -// Placeholder for something like api.lbry.io/reward_type/get/[name] */ -function apiRewardTypeGet(name) { - return { - name: 'reward1', - title: 'Reward 1', - description: 'Reward 1 description', - value: 50, - claimed: true, - }; -} +const {shell} = require('electron'); +const querystring = require('querystring'); + +const GITHUB_CLIENT_ID = '6baf581d32bad60519'; + +const LinkGithubReward = React.createClass({ + propTypes: { + onClaimed: React.PropTypes.func, + }, + _launchLinkPage: function() { + const githubAuthParams = { + client_id: GITHUB_CLIENT_ID, + redirect_uri: 'https://lbry.io/', + scope: 'user:email,public_repo', + allow_signup: false, + } + shell.openExternal('https://github.com/login/oauth/authorize?' + querystring.stringify(githubAuthParams)); + }, + handleConfirmClicked: function() { + this.setState({ + confirming: true, + }); + + lbryio.call('reward', 'new', { + reward_type: 'new_developer', + access_token: 'token will go here', + }, 'post').then((response) => { + console.log('response:', response); + + this.props.onClaimed(); // This will trigger another API call to show that we succeeded + + this.setState({ + confirming: false, + }); + }, (error) => { + console.log('failed with error:', error); + this.setState({ + confirming: false, + }); + }); + }, + getInitialState: function() { + return { + confirming: false, + }; + }, + render: function() { + return ( +
+
+

+

This will open browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.

+

Once you're finished, you may confirm you've linked the account to receive your reward.

+
+ + +
+ ); + } +}); const RewardPage = React.createClass({ propTypes: { - name: React.PropTypes.string, + name: React.PropTypes.string.isRequired, + }, + _getRewardType: function() { + lbryio.call('reward_type', 'get', this.props.name).then((rewardType) => { + this.setState({ + rewardType: rewardType, + }); + }); }, getInitialState: function() { return { @@ -21,22 +82,28 @@ const RewardPage = React.createClass({ }; }, componentWillMount: function() { - this.setState({ - rewardType: apiRewardTypeGet(this.props.name), - }); + this._getRewardType(); }, render: function() { if (!this.state.rewardType) { return null; } - let {title, description, value} = this.state.rewardType; + let Reward; + if (this.props.name == 'link_github') { + Reward = LinkGithubReward; + } + + const {title, description, value} = this.state.rewardType; return (

{title}

-

{description}

- {/* Most likely have a component included here for each reward (e.g. WatchVideoReward) */} + +

{this.state.rewardType.claimed + ? This reward has been claimed. + : description}

+
); diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js index 093821f31..6deefa005 100644 --- a/ui/js/page/rewards.js +++ b/ui/js/page/rewards.js @@ -1,31 +1,24 @@ import React from 'react'; import lbry from '../lbry.js'; +import {CreditAmount} from '../component/common.js'; import Modal from '../component/modal.js'; import {Link} from '../component/link.js'; +import lbryio from '../lbryio.js'; -// Placeholder for something like api.lbry.io/reward_type/list */ -function apiRewardTypeList() { - return [ - { - name: 'link_github', - title: 'Link your GitHub account', - description: 'Link LBRY to your GitHub account', - value: 50, - claimed: false, - }, - ]; -} - -var RewardTile = React.createClass({ +const RewardTile = React.createClass({ propTypes: { - name: React.PropTypes.string, - title: React.PropTypes.string, + name: React.PropTypes.string.isRequired, + title: React.PropTypes.string.isRequired, + description: React.PropTypes.string.isRequired, + claimed: React.PropTypes.bool.isRequired, + value: React.PropTypes.number.isRequired, }, render: function() { return (

+
{this.props.description}
{this.props.claimed ? This reward has been claimed. @@ -38,8 +31,10 @@ var RewardTile = React.createClass({ var RewardsPage = React.createClass({ componentWillMount: function() { - this.setState({ - rewardTypes: apiRewardTypeList(), + lbryio.call('reward_type', 'list', {}).then((rewardTypes) => { + this.setState({ + rewardTypes: rewardTypes, + }); }); }, getInitialState: function() { @@ -56,7 +51,7 @@ var RewardsPage = React.createClass({ {!this.state.rewardTypes ? null : this.state.rewardTypes.map(({name, title, description, claimed, value}) => { - return ; + return ; })}
diff --git a/ui/scss/all.scss b/ui/scss/all.scss index 6012fc3ee..f90d7cd06 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -12,4 +12,5 @@ @import "component/_load-screen.scss"; @import "component/_channel-indicator.scss"; @import "page/_developer.scss"; -@import "page/_watch.scss"; \ No newline at end of file +@import "page/_watch.scss"; +@import "page/_reward.scss"; diff --git a/ui/scss/page/_reward.scss b/ui/scss/page/_reward.scss new file mode 100644 index 000000000..739e86619 --- /dev/null +++ b/ui/scss/page/_reward.scss @@ -0,0 +1,3 @@ +.reward-page__details { + background-color: rgba(0, 0, 0, 0.01); +} \ No newline at end of file From c374a59af8fa9394b39be2a815c43aa5f4bb5398 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 30 Mar 2017 00:48:18 -0400 Subject: [PATCH 03/51] Add access token and wallet address to GitHub reward --- ui/js/lbryio.js | 7 +------ ui/js/page/reward.js | 36 ++++++++++++++++++++---------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index c65767aaa..777f70f29 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -28,18 +28,13 @@ const mocks = { }; lbryio.call = function(resource, action, params, method='get') { - console.log('top of lbryio.call') return new Promise((resolve, reject) => { - console.log('top of promise handler') /* temp code for mocks */ if (`${resource}.${action}` in mocks) { - console.log(`found ${resource}.${action} in mocks`) resolve(mocks[`${resource}.${action}`](params)); - console.log('...resolved.'); return; - } else { - console.log(`did not find ${resource}.${action} in mocks`); } + /* end temp */ console.log('about to create xhr object'); diff --git a/ui/js/page/reward.js b/ui/js/page/reward.js index 8c148ad80..55562c8eb 100644 --- a/ui/js/page/reward.js +++ b/ui/js/page/reward.js @@ -13,34 +13,38 @@ const LinkGithubReward = React.createClass({ onClaimed: React.PropTypes.func, }, _launchLinkPage: function() { - const githubAuthParams = { + /* const githubAuthParams = { client_id: GITHUB_CLIENT_ID, redirect_uri: 'https://lbry.io/', scope: 'user:email,public_repo', allow_signup: false, } - shell.openExternal('https://github.com/login/oauth/authorize?' + querystring.stringify(githubAuthParams)); + shell.openExternal('https://github.com/login/oauth/authorize?' + querystring.stringify(githubAuthParams)); */ + shell.openExternal('https://lbry.io'); }, handleConfirmClicked: function() { this.setState({ confirming: true, }); - lbryio.call('reward', 'new', { - reward_type: 'new_developer', - access_token: 'token will go here', - }, 'post').then((response) => { - console.log('response:', response); + lbry.get_new_address().then((address) => { + lbryio.call('reward', 'new', { + reward_type: 'new_developer', + access_token: '**access token here**', + wallet_address: address, + }, 'post').then((response) => { + console.log('response:', response); - this.props.onClaimed(); // This will trigger another API call to show that we succeeded + this.props.onClaimed(); // This will trigger another API call to show that we succeeded - this.setState({ - confirming: false, - }); - }, (error) => { - console.log('failed with error:', error); - this.setState({ - confirming: false, + this.setState({ + confirming: false, + }); + }, (error) => { + console.log('failed with error:', error); + this.setState({ + confirming: false, + }); }); }); }, @@ -54,7 +58,7 @@ const LinkGithubReward = React.createClass({

-

This will open browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.

+

This will open a browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.

Once you're finished, you may confirm you've linked the account to receive your reward.

From 8299e229fdcf412b810c69b74a0009223ff4dd62 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 30 Mar 2017 14:05:37 -0400 Subject: [PATCH 04/51] Improve lbry.io API code - Send as form data - Handle errors better --- ui/js/lbryio.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 777f70f29..7271462a0 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -57,18 +57,17 @@ lbryio.call = function(resource, action, params, method='get') { console.log('loaded'); const response = JSON.parse(xhr.responseText); - if (response.error) { + if (!response.success) { if (reject) { reject(new Error(response.error)); } else { document.dispatchEvent(new CustomEvent('unhandledError', { detail: { connectionString: connectionString, - method: method, + method: action, params: params, - code: response.error.code, message: response.error.message, - data: response.error.data, + ... response.error.data ? {data: response.error.data} : {}, } })); } @@ -79,6 +78,11 @@ lbryio.call = function(resource, action, params, method='get') { console.log('about to call xhr.open'); xhr.open(method, CONNECTION_STRING + resource + '/' + action, true); + + if (method == 'post') { + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + } + xhr.send(querystring.stringify(params)); }); }; From 3749e0393acc132fbe85e15540982acd49f87211 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 30 Mar 2017 14:08:27 -0400 Subject: [PATCH 05/51] Reward: add error handling Also adds Notice component --- ui/js/component/notice.js | 25 +++++++++++++++++++++++++ ui/js/page/reward.js | 9 +++++++++ ui/scss/all.scss | 1 + ui/scss/component/_notice.scss | 14 ++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 ui/js/component/notice.js create mode 100644 ui/scss/component/_notice.scss diff --git a/ui/js/component/notice.js b/ui/js/component/notice.js new file mode 100644 index 000000000..2e5812c21 --- /dev/null +++ b/ui/js/component/notice.js @@ -0,0 +1,25 @@ +import React from 'react'; +import lbry from '../lbry.js'; +import {Link} from '../component/link.js'; +import {FileActions} from '../component/file-actions.js'; +import {Thumbnail, TruncatedText, CreditAmount} from '../component/common.js'; + +export const Notice = React.createClass({ + propTypes: { + isError: React.PropTypes.bool, + }, + getDefaultProps: function() { + return { + isError: false, + }; + }, + render: function() { + return ( +
+ {this.props.children} +
+ ); + }, +}); + +export default Notice; \ No newline at end of file diff --git a/ui/js/page/reward.js b/ui/js/page/reward.js index 55562c8eb..a828d6f9f 100644 --- a/ui/js/page/reward.js +++ b/ui/js/page/reward.js @@ -1,6 +1,7 @@ import React from 'react'; import lbryio from '../lbryio.js'; import {Link} from '../component/link.js'; +import Notice from '../component/notice.js'; import {CreditAmount} from '../component/common.js'; const {shell} = require('electron'); @@ -39,11 +40,13 @@ const LinkGithubReward = React.createClass({ this.setState({ confirming: false, + error: null, }); }, (error) => { console.log('failed with error:', error); this.setState({ confirming: false, + error: error, }); }); }); @@ -51,6 +54,7 @@ const LinkGithubReward = React.createClass({ getInitialState: function() { return { confirming: false, + error: null, }; }, render: function() { @@ -61,6 +65,11 @@ const LinkGithubReward = React.createClass({

This will open a browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.

Once you're finished, you may confirm you've linked the account to receive your reward.

+ {this.state.error + ? + {this.state.error.message} + + : null} diff --git a/ui/scss/all.scss b/ui/scss/all.scss index f90d7cd06..0616bdf4b 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -14,3 +14,4 @@ @import "page/_developer.scss"; @import "page/_watch.scss"; @import "page/_reward.scss"; +@import "component/_notice.scss"; diff --git a/ui/scss/component/_notice.scss b/ui/scss/component/_notice.scss new file mode 100644 index 000000000..2ae4f403e --- /dev/null +++ b/ui/scss/component/_notice.scss @@ -0,0 +1,14 @@ +@import "../global"; + +.notice { + padding: 10px 20px; + border: 1px solid #000; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + border-radius: 5px; +} + +.notice--error { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; +} From 892607174a0c7aae8d86598e3b93e7cba0ae7dcf Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 30 Mar 2017 14:09:42 -0400 Subject: [PATCH 06/51] Reward: style and formatting tweaks --- ui/js/page/reward.js | 2 +- ui/scss/page/_reward.scss | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/js/page/reward.js b/ui/js/page/reward.js index a828d6f9f..773e21893 100644 --- a/ui/js/page/reward.js +++ b/ui/js/page/reward.js @@ -60,8 +60,8 @@ const LinkGithubReward = React.createClass({ render: function() { return (
+

-

This will open a browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.

Once you're finished, you may confirm you've linked the account to receive your reward.

diff --git a/ui/scss/page/_reward.scss b/ui/scss/page/_reward.scss index 739e86619..a550c01c3 100644 --- a/ui/scss/page/_reward.scss +++ b/ui/scss/page/_reward.scss @@ -1,3 +1,5 @@ +@import "../global"; + .reward-page__details { - background-color: rgba(0, 0, 0, 0.01); + background-color: lighten($color-canvas, 1.5%); } \ No newline at end of file From b0742427832d30cf82f3a56618d81061c374f64b Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 30 Mar 2017 15:05:31 -0400 Subject: [PATCH 07/51] Quick cleanup in main.js Friends don't let friends mix tabs and spaces --- ui/js/main.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/ui/js/main.js b/ui/js/main.js index f00c49a69..5d397bb26 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -25,22 +25,23 @@ var init = function() { ReactDOM.render(, canvas) } else { ReactDOM.render( - { if (balance <= 0) { - window.location.href = '?claim'; + window.location.href = '?claim'; } else { - ReactDOM.render(, canvas); + ReactDOM.render(, canvas); } }); - } else { + } else { ReactDOM.render(, canvas); - } - }}/>, - canvas - ); + } + }}/> + ), + canvas); } }; From 98b38855a27bab4a9cbbdc74653433eda9ef1838 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 30 Mar 2017 19:00:33 -0400 Subject: [PATCH 08/51] Progress toward register page --- ui/js/app.js | 6 +++++- ui/js/lbryio.js | 14 ++++++++------ ui/js/main.js | 36 ++++++++++++++++++++++++++++++------ 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/ui/js/app.js b/ui/js/app.js index 2677c0b91..d7ca70f5d 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -2,6 +2,7 @@ import React from 'react'; import {Line} from 'rc-progress'; import lbry from './lbry.js'; +import RegisterPage from './page/register.js'; import SettingsPage from './page/settings.js'; import HelpPage from './page/help.js'; import WatchPage from './page/watch.js'; @@ -40,6 +41,7 @@ var App = React.createClass({ message: 'Error message', data: 'Error data', }, + _fullScreenPages: ['register', 'watch'], _upgradeDownloadItem: null, _isMounted: false, @@ -258,6 +260,8 @@ var App = React.createClass({ { switch(this.state.viewingPage) { + case 'register': + return ; case 'settings': return ; case 'help': @@ -301,7 +305,7 @@ var App = React.createClass({ searchQuery = this.state.viewingPage == 'discover' && this.state.pageArgs ? this.state.pageArgs : ''; return ( - this.state.viewingPage == 'watch' ? + this._fullScreenPages.includes(this.state.viewingPage) ? mainContent :
diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 7271462a0..0becbf487 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -2,7 +2,7 @@ const querystring = require('querystring'); const lbryio = {}; -const CONNECTION_STRING = 'https://api.lbry.io/'; +const CONNECTION_STRING = 'https://apidev.lbry.tech/'; const mocks = { 'reward_type.get': (name) => { @@ -72,18 +72,20 @@ lbryio.call = function(resource, action, params, method='get') { })); } } else { - resolve(response.result); + resolve(response.data); } }); console.log('about to call xhr.open'); - xhr.open(method, CONNECTION_STRING + resource + '/' + action, true); - if (method == 'post') { + if (method == 'get') { + xhr.open('get', CONNECTION_STRING + resource + '/' + action + '?' + querystring.stringify(params), true); + xhr.send(); + } else if (method == 'post') { + xhr.open('post', CONNECTION_STRING + resource + '/' + action, true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send(querystring.stringify(params)); } - - xhr.send(querystring.stringify(params)); }); }; diff --git a/ui/js/main.js b/ui/js/main.js index 5d397bb26..9e10b1b41 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import lbry from './lbry.js'; +import lbryio from './lbryio.js'; import lighthouse from './lighthouse.js'; import App from './app.js'; import SplashScreen from './component/splash.js'; @@ -16,7 +17,7 @@ window.addEventListener('contextmenu', (event) => { event.preventDefault(); }); -var init = function() { +let init = function() { window.lbry = lbry; window.lighthouse = lighthouse; @@ -27,8 +28,10 @@ var init = function() { ReactDOM.render( ( { if (balance <= 0) { window.location.href = '?claim'; @@ -40,9 +43,30 @@ var init = function() { ReactDOM.render(, canvas); } }}/> - ), - canvas); + ), canvas); } }; -init(); +if (localStorage.getItem('accessToken') || window.location.search == '?register') { + // User is already registered, or on the registration page + init(); +} else { + // Send + lbry.status().then(({installation_id}) => { + installation_id += parseInt(Date.now(), 10); // temp + installation_id += "X".repeat(96 - installation_id.length); // temp + lbryio.call('user_install', 'exists', {app_id: installation_id}).then((userExists) => { + if (userExists) { + /* TODO: somehow user exists with the same installation ID, but we don't have the token recorded. What do we do here? */ + } else { + lbryio.call('user', 'new', { + language: 'en', + app_id: installation_id, + }, 'post').then(({ID}) => { + localStorage.setItem('accessToken', ID); + window.location = '?register'; + }); + } + }); + }); +} From 8964398c2e2b8f5098359033318feafd14e050ed Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 30 Mar 2017 19:06:06 -0400 Subject: [PATCH 09/51] Register page fixes --- ui/js/page/register.js | 48 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 ui/js/page/register.js diff --git a/ui/js/page/register.js b/ui/js/page/register.js new file mode 100644 index 000000000..929058b3c --- /dev/null +++ b/ui/js/page/register.js @@ -0,0 +1,48 @@ +import React from 'react'; +import lbryio from '../lbryio.js'; +import {getLocal, setLocal} from '../utils.js'; +import FormField from '../component/form.js' +import {Link} from '../component/link.js' + +const RegisterPage = React.createClass({ + _getRewardType: function() { + lbryio.call('reward_type', 'get', this.props.name).then((rewardType) => { + this.setState({ + rewardType: rewardType, + }); + }); + }, + handleSubmit: function(event) { + if (event !== 'undefined') { + event.preventDefault(); + } + + if (!this.state.email) { + this._emailField.warnRequired(); + } + }, + getInitialState: function() { + return { + rewardType: null, + email: null, + }; + }, + componentWillMount: function() { + this._getRewardType(); + }, + render: function() { + return ( +
+
+

Register a LBRY account

+
+
+
+
+
+
+ ); + } +}); + +export default RegisterPage; From 5587d2f0f5eef73a599380cb3b6f5d0c890abc61 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sat, 1 Apr 2017 02:30:32 -0400 Subject: [PATCH 10/51] Convert Register page to Email page --- ui/js/app.js | 9 +++++---- ui/js/page/{register.js => email.js} | 13 +++---------- 2 files changed, 8 insertions(+), 14 deletions(-) rename ui/js/page/{register.js => email.js} (77%) diff --git a/ui/js/app.js b/ui/js/app.js index d7ca70f5d..517008afa 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -2,7 +2,8 @@ import React from 'react'; import {Line} from 'rc-progress'; import lbry from './lbry.js'; -import RegisterPage from './page/register.js'; +import lbryio from './lbryio.js'; +import EmailPage from './page/email.js'; import SettingsPage from './page/settings.js'; import HelpPage from './page/help.js'; import WatchPage from './page/watch.js'; @@ -41,7 +42,7 @@ var App = React.createClass({ message: 'Error message', data: 'Error data', }, - _fullScreenPages: ['register', 'watch'], + _fullScreenPages: ['watch'], _upgradeDownloadItem: null, _isMounted: false, @@ -260,8 +261,6 @@ var App = React.createClass({ { switch(this.state.viewingPage) { - case 'register': - return ; case 'settings': return ; case 'help': @@ -292,6 +291,8 @@ var App = React.createClass({ return ; case 'publish': return ; + case 'email': + return ; case 'developer': return ; case 'discover': diff --git a/ui/js/page/register.js b/ui/js/page/email.js similarity index 77% rename from ui/js/page/register.js rename to ui/js/page/email.js index 929058b3c..926e253d8 100644 --- a/ui/js/page/register.js +++ b/ui/js/page/email.js @@ -4,16 +4,9 @@ import {getLocal, setLocal} from '../utils.js'; import FormField from '../component/form.js' import {Link} from '../component/link.js' -const RegisterPage = React.createClass({ - _getRewardType: function() { - lbryio.call('reward_type', 'get', this.props.name).then((rewardType) => { - this.setState({ - rewardType: rewardType, - }); - }); - }, +const EmailPage = React.createClass({ handleSubmit: function(event) { - if (event !== 'undefined') { + if (event !== undefined) { event.preventDefault(); } @@ -45,4 +38,4 @@ const RegisterPage = React.createClass({ } }); -export default RegisterPage; +export default EmailPage; From 3727e275c468d73621fd73988ca15589013fe2c8 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sat, 1 Apr 2017 02:36:45 -0400 Subject: [PATCH 11/51] Update registration logic and move to app.js --- ui/js/app.js | 42 +++++++++++++++++++++++++++++++++++++++++- ui/js/main.js | 24 +----------------------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/ui/js/app.js b/ui/js/app.js index 517008afa..353846266 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -88,6 +88,32 @@ var App = React.createClass({ pageArgs: pageArgs === undefined ? null : pageArgs }; }, + updateRegistrationStatus: function() { + if (localStorage.getItem('accessToken')) { + this.setState({ + registrationCheckComplete: true, + }); + } else { + lbry.status().then(({installation_id}) => { + installation_id += parseInt(Date.now(), 10); // temp + installation_id += "X".repeat(96 - installation_id.length); // temp + lbryio.call('user_install', 'exists', {app_id: installation_id}).then((userExists) => { + // TODO: deal with case where user exists already with the same app ID, but we have no access token. + // Possibly merge in to the existing user with the same app ID. + lbryio.call('user', 'new', { + language: 'en', + app_id: installation_id, + }, 'post').then(({ID}) => { + localStorage.setItem('accessToken', ID); + this.setState({ + registrationCheckComplete: true, + justRegistered: true, + }); + }); + }) + }); + } + }, getInitialState: function() { var match, param, val, viewingPage, pageArgs, drawerOpenRaw = sessionStorage.getItem('drawerOpen'); @@ -98,9 +124,17 @@ var App = React.createClass({ modal: null, downloadProgress: null, downloadComplete: false, + registrationCheckComplete: null, + justRegistered: false, }); }, componentWillMount: function() { + if (!localStorage.getItem('accessToken') && window.location.search != '?discover') { + // User isn't registered but somehow made it to a page other than Discover, so send them to + // Discover to get them registered and show them the welcome screen. + window.location.search = '?discover'; + } + document.addEventListener('unhandledError', (event) => { this.alertError(event.detail); }); @@ -138,6 +172,8 @@ var App = React.createClass({ }); }); } + + this.updateRegistrationStatus(); }, openDrawer: function() { sessionStorage.setItem('drawerOpen', true); @@ -297,10 +333,14 @@ var App = React.createClass({ return ; case 'discover': default: - return ; + return ; } }, render: function() { + if (!this.state.registrationCheckComplete) { + return null; + } + var mainContent = this.getMainContent(), headerLinks = this.getHeaderLinks(), searchQuery = this.state.viewingPage == 'discover' && this.state.pageArgs ? this.state.pageArgs : ''; diff --git a/ui/js/main.js b/ui/js/main.js index 9e10b1b41..ea8d4d0a5 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -47,26 +47,4 @@ let init = function() { } }; -if (localStorage.getItem('accessToken') || window.location.search == '?register') { - // User is already registered, or on the registration page - init(); -} else { - // Send - lbry.status().then(({installation_id}) => { - installation_id += parseInt(Date.now(), 10); // temp - installation_id += "X".repeat(96 - installation_id.length); // temp - lbryio.call('user_install', 'exists', {app_id: installation_id}).then((userExists) => { - if (userExists) { - /* TODO: somehow user exists with the same installation ID, but we don't have the token recorded. What do we do here? */ - } else { - lbryio.call('user', 'new', { - language: 'en', - app_id: installation_id, - }, 'post').then(({ID}) => { - localStorage.setItem('accessToken', ID); - window.location = '?register'; - }); - } - }); - }); -} +init(); From 7f8bf8a2e2b3424ece6f3263d3211d39514923e6 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sat, 1 Apr 2017 02:51:15 -0400 Subject: [PATCH 12/51] Add Welcome screen --- ui/js/component/modal-page.js | 18 ++++++++++++++++ ui/js/component/welcome.js | 24 ++++++++++++++++++++++ ui/js/page/discover.js | 19 +++++++++++++++++ ui/scss/all.scss | 1 + ui/scss/component/_modal-page.scss | 33 ++++++++++++++++++++++++++++++ 5 files changed, 95 insertions(+) create mode 100644 ui/js/component/modal-page.js create mode 100644 ui/js/component/welcome.js create mode 100644 ui/scss/component/_modal-page.scss diff --git a/ui/js/component/modal-page.js b/ui/js/component/modal-page.js new file mode 100644 index 000000000..d0def4b82 --- /dev/null +++ b/ui/js/component/modal-page.js @@ -0,0 +1,18 @@ +import React from 'react'; +import ReactModal from 'react-modal'; + +export const ModalPage = React.createClass({ + render: function() { + return ( + +
+ {this.props.children} +
+
+ ); + } +}); + +export default ModalPage; \ No newline at end of file diff --git a/ui/js/component/welcome.js b/ui/js/component/welcome.js new file mode 100644 index 000000000..885bfaf3e --- /dev/null +++ b/ui/js/component/welcome.js @@ -0,0 +1,24 @@ +import React from 'react'; +import ModalPage from './modal-page.js'; +import {Link} from '../component/link.js'; + +export const Welcome = React.createClass({ + propTypes: { + onDone: React.PropTypes.func.isRequired, + }, + handleOKClicked: function() { + this.props.onDone(); + }, + render: function() { + return ( + +

Welcome to LBRY

+ Content will go here... +
+ +
+
+ ); + } +}); + diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js index 678310338..a53d55895 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover.js @@ -5,6 +5,7 @@ import {FileTile} from '../component/file-tile.js'; import {Link} from '../component/link.js'; import {ToolTip} from '../component/tooltip.js'; import {BusyMessage} from '../component/common.js'; +import {Welcome} from '../component/welcome.js'; var fetchResultsStyle = { color: '#888', @@ -105,6 +106,10 @@ var FeaturedContent = React.createClass({ var DiscoverPage = React.createClass({ userTypingTimer: null, + propTypes: { + showWelcome: React.PropTypes.bool.isRequired, + }, + componentDidUpdate: function() { if (this.props.query != this.state.query) { @@ -112,6 +117,12 @@ var DiscoverPage = React.createClass({ } }, + getDefaultProps: function() { + return { + showWelcome: false, + } + }, + componentWillReceiveProps: function(nextProps, nextState) { if (nextProps.query != nextState.query) { @@ -128,6 +139,12 @@ var DiscoverPage = React.createClass({ lighthouse.search(query).then(this.searchCallback); }, + handleWelcomeDone: function() { + this.setState({ + welcomeComplete: true, + }); + }, + componentWillMount: function() { document.title = "Discover"; if (this.props.query) { @@ -138,6 +155,7 @@ var DiscoverPage = React.createClass({ getInitialState: function() { return { + welcomeComplete: false, results: [], query: this.props.query, searching: ('query' in this.props) && (this.props.query.length > 0) @@ -161,6 +179,7 @@ var DiscoverPage = React.createClass({ { !this.state.searching && this.props.query && this.state.results.length ? : null } { !this.state.searching && this.props.query && !this.state.results.length ? : null } { !this.props.query && !this.state.searching ? : null } + ); } diff --git a/ui/scss/all.scss b/ui/scss/all.scss index 0616bdf4b..0c58b94df 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -11,6 +11,7 @@ @import "component/_tooltip.scss"; @import "component/_load-screen.scss"; @import "component/_channel-indicator.scss"; +@import "component/_modal-page.scss"; @import "page/_developer.scss"; @import "page/_watch.scss"; @import "page/_reward.scss"; diff --git a/ui/scss/component/_modal-page.scss b/ui/scss/component/_modal-page.scss new file mode 100644 index 000000000..1acc41790 --- /dev/null +++ b/ui/scss/component/_modal-page.scss @@ -0,0 +1,33 @@ +.modal-page-overlay { + position: fixed; + display: flex; + justify-content: center; + align-items: center; + + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + background-color: rgba(255, 255, 255, 0.74902); + z-index: 9999; +} + +.modal-page { + position: fixed; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + border: 1px solid rgb(204, 204, 204); + background: rgb(255, 255, 255); + overflow: auto; + border-radius: 4px; + outline: none; + padding: 36px; + + top: 25px; + left: 25px; + right: 25px; + bottom: 25px; +} From e0f05f43a6a95509a6c0c217e4689ed33826edd1 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sat, 1 Apr 2017 02:52:45 -0400 Subject: [PATCH 13/51] Log requests and responses in lbryio module --- ui/js/lbryio.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 0becbf487..79df9aa07 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -72,6 +72,7 @@ lbryio.call = function(resource, action, params, method='get') { })); } } else { + console.info(`${resource}.${action} response data:`, response); resolve(response.data); } }); @@ -79,9 +80,11 @@ lbryio.call = function(resource, action, params, method='get') { console.log('about to call xhr.open'); if (method == 'get') { + console.info('GET ', CONNECTION_STRING + resource + '/' + action, ' | params:', params); xhr.open('get', CONNECTION_STRING + resource + '/' + action + '?' + querystring.stringify(params), true); xhr.send(); } else if (method == 'post') { + console.info('POST ', CONNECTION_STRING + resource + '/' + action, '| params: ', params); xhr.open('post', CONNECTION_STRING + resource + '/' + action, true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.send(querystring.stringify(params)); From 1dbbf8fc0126f72c8bfea16f812bdb5b8c915634 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sat, 1 Apr 2017 02:54:56 -0400 Subject: [PATCH 14/51] Style tweaks --- ui/js/app.js | 1 - ui/js/lbryio.js | 1 + ui/js/page/discover.js | 1 + ui/js/page/rewards.js | 2 +- ui/scss/all.scss | 2 +- 5 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/js/app.js b/ui/js/app.js index 353846266..697c62ca8 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -48,7 +48,6 @@ var App = React.createClass({ _isMounted: false, _version: null, - // Temporary workaround since electron-dl throws errors when you try to get the filename getDefaultProps: function() { return { address: window.location.search diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 79df9aa07..63bf08888 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -39,6 +39,7 @@ lbryio.call = function(resource, action, params, method='get') { console.log('about to create xhr object'); const xhr = new XMLHttpRequest; + xhr.addEventListener('error', function (error) { console.log('received XHR error:', error); reject(error); diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js index a53d55895..c6705d871 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover.js @@ -147,6 +147,7 @@ var DiscoverPage = React.createClass({ componentWillMount: function() { document.title = "Discover"; + if (this.props.query) { // Rendering with a query already typed this.handleSearchChanged(this.props.query); diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js index 6deefa005..52f705035 100644 --- a/ui/js/page/rewards.js +++ b/ui/js/page/rewards.js @@ -1,9 +1,9 @@ import React from 'react'; import lbry from '../lbry.js'; +import lbryio from '../lbryio.js'; import {CreditAmount} from '../component/common.js'; import Modal from '../component/modal.js'; import {Link} from '../component/link.js'; -import lbryio from '../lbryio.js'; const RewardTile = React.createClass({ propTypes: { diff --git a/ui/scss/all.scss b/ui/scss/all.scss index 0c58b94df..126ad742c 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -11,8 +11,8 @@ @import "component/_tooltip.scss"; @import "component/_load-screen.scss"; @import "component/_channel-indicator.scss"; +@import "component/_notice.scss"; @import "component/_modal-page.scss"; @import "page/_developer.scss"; @import "page/_watch.scss"; @import "page/_reward.scss"; -@import "component/_notice.scss"; From 70d2f7c823d3443832b670d164e72537b5e82865 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 2 Apr 2017 05:55:24 -0400 Subject: [PATCH 15/51] Add auth to lbryio.js --- ui/js/app.js | 1 + ui/js/lbryio.js | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ui/js/app.js b/ui/js/app.js index 697c62ca8..5572550ff 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -104,6 +104,7 @@ var App = React.createClass({ app_id: installation_id, }, 'post').then(({ID}) => { localStorage.setItem('accessToken', ID); + localStorage.setItem('appId', installation_id); this.setState({ registrationCheckComplete: true, justRegistered: true, diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 63bf08888..7c6771aac 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -80,15 +80,22 @@ lbryio.call = function(resource, action, params, method='get') { console.log('about to call xhr.open'); + // For social media auth: + //const accessToken = localStorage.getItem('accessToken'); + //const fullParams = {...params, ... accessToken ? {access_token: accessToken} : {}}; + + // Temp app ID based auth: + const fullParams = {app_id: localStorage.getItem('appId'), ...params}; + if (method == 'get') { - console.info('GET ', CONNECTION_STRING + resource + '/' + action, ' | params:', params); - xhr.open('get', CONNECTION_STRING + resource + '/' + action + '?' + querystring.stringify(params), true); + console.info('GET ', CONNECTION_STRING + resource + '/' + action, ' | params:', fullParams); + xhr.open('get', CONNECTION_STRING + resource + '/' + action + '?' + querystring.stringify(fullParams), true); xhr.send(); } else if (method == 'post') { - console.info('POST ', CONNECTION_STRING + resource + '/' + action, '| params: ', params); + console.info('POST ', CONNECTION_STRING + resource + '/' + action, '| params: ', fullParams); xhr.open('post', CONNECTION_STRING + resource + '/' + action, true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - xhr.send(querystring.stringify(params)); + xhr.send(querystring.stringify(fullParams)); } }); }; From b975fab1bb45c23bf15330c2222b344e99004bd1 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 2 Apr 2017 06:00:40 -0400 Subject: [PATCH 16/51] Add email section to Welcome screen --- ui/js/component/welcome.js | 169 +++++++++++++++++++++++++++++++++++-- 1 file changed, 160 insertions(+), 9 deletions(-) diff --git a/ui/js/component/welcome.js b/ui/js/component/welcome.js index 885bfaf3e..7694997c0 100644 --- a/ui/js/component/welcome.js +++ b/ui/js/component/welcome.js @@ -1,21 +1,172 @@ import React from 'react'; +import lbryio from '../lbryio.js'; + import ModalPage from './modal-page.js'; import {Link} from '../component/link.js'; +import FormField from '../component/form.js'; +import Notice from '../component/notice.js' -export const Welcome = React.createClass({ - propTypes: { - onDone: React.PropTypes.func.isRequired, - }, - handleOKClicked: function() { - this.props.onDone(); +const IntroStage = React.createClass({ + componentWillMount: function() { + this.props.onCompleted(); // Nothing required to move on }, render: function() { return ( - +

Welcome to LBRY

- Content will go here... +

Content will go here...

+
+ ); + } +}); + +const SubmitEmailStage = React.createClass({ + getInitialState: function() { + return { + rewardType: null, + email: '', + submitting: false, + }; + }, + handleEmailChanged: function(event) { + this.setState({ + email: event.target.value, + }); + }, + handleSubmit: function(event) { + event.preventDefault(); + + this.setState({ + submitting: true, + }); + lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => { + this.setState({ + submitting: false, + message: "Your email has been verified.", + success: true, + }); + this.props.onCompleted(); + }, (error) => { + this.setState({ + submitting: false, + message: error.message, + success: false, + }); + }); + }, + render: function() { + return ( +
+

Verify Your Email Address

+ {this.state.message + ? + {this.state.message} + + : null} +

Copy here explaining what we do with your email, and the reward.

+
+
+
+
+
+ ); + } +}); + +/* const ConfirmEmailStage = React.createClass({ + getInitialState: function() { + return { + rewardType: null, + email: '', + submitting: false, + }; + }, + handleEmailChanged: function(event) { + this.setState({ + email: event.target.value, + }); + }, + handleSubmit: function(event) { + event.preventDefault(); + // ... + }, + render: function() { + return ( +
+

Confirm Your Email Address

+ {this.state.message + ? + {this.state.message} + + : null} +

Ask the user to take steps needed to confirm (click link in confirmation email, etc.)

+
+
+
+
+
+ ); + } +}); */ + +const FinalMessageStage = React.createClass({ + componentWillMount: function() { + this.props.onCompleted(); + }, + render: function() { + return ( +
+

Email verified

+

Text here about what happens next

+
+ ); + } +}); + +export const Welcome = React.createClass({ + _stages: [ + IntroStage, + SubmitEmailStage, + //ConfirmEmailStage, + FinalMessageStage, + ], + propTypes: { + onDone: React.PropTypes.func.isRequired, + }, + getInitialState: function() { + return { + stageNum: 0, + }; + }, + handleNextClicked: function() { + if (this.state.stageNum >= this._stages.length - 1) { + this.props.onDone(); + } + + this.setState({ + stageNum: this.state.stageNum + 1, + stageCompleted: false, + }); + }, + handleDoneClicked: function() { + this.props.onDone(); + }, + handleStageComplete: function() { + console.log('inside handleStageComplete') + this.setState({ + stageCompleted: true, + }); + }, + render: function() { + const Content = this._stages[this.state.stageNum]; + const isLastStage = this.state.stageNum >= this._stages.length - 1; + return ( + +
- + {!isLastStage + ? + : }
); From 7b7e361bddd36f6973015e7ab06bb6699a55d3e4 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 2 Apr 2017 06:02:53 -0400 Subject: [PATCH 17/51] Add notification bar Used for displaying global info (e.g. "you just got a reward.") Can be displayed from anywhere in the app using events. --- ui/js/component/header.js | 2 + ui/js/component/notice.js | 2 +- ui/js/component/notification-bar.js | 53 ++++++++++++++++++++++++ ui/scss/all.scss | 1 + ui/scss/component/_notice.scss | 4 ++ ui/scss/component/_notification-bar.scss | 6 +++ 6 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 ui/js/component/notification-bar.js create mode 100644 ui/scss/component/_notification-bar.scss diff --git a/ui/js/component/header.js b/ui/js/component/header.js index fb64e9ece..6e186cc44 100644 --- a/ui/js/component/header.js +++ b/ui/js/component/header.js @@ -1,5 +1,6 @@ import React from 'react'; import {Link} from './link.js'; +import NotificationBar from './notification-bar.js'; var Header = React.createClass({ getInitialState: function() { @@ -61,6 +62,7 @@ var Header = React.createClass({ : '' } + ); } diff --git a/ui/js/component/notice.js b/ui/js/component/notice.js index 2e5812c21..a09238294 100644 --- a/ui/js/component/notice.js +++ b/ui/js/component/notice.js @@ -15,7 +15,7 @@ export const Notice = React.createClass({ }, render: function() { return ( -
+
{this.props.children}
); diff --git a/ui/js/component/notification-bar.js b/ui/js/component/notification-bar.js new file mode 100644 index 000000000..f6662b552 --- /dev/null +++ b/ui/js/component/notification-bar.js @@ -0,0 +1,53 @@ +import React from 'react'; +import lbry from '../lbry.js'; +import Notice from '../component/notice.js'; + +export const NotificationBar = React.createClass({ + _displayTime: 8, // in seconds + + _hideTimeout: null, + + getInitialState: function() { + return { + message: null, + isError: null, + } + }, + handleNoticeReceived: function(event) { + if (this._hideTimeout) { + clearTimeout(this._hideTimeout); + } + + const {detail: {message, isError}} = event; + this.setState({ + message: message, + isError: isError, + }); + + this._hideTimeout = setTimeout(() => { + this.setState({ + message: null, + isError: null, + }); + }, this._displayTime * 1000); + }, + componentWillMount: function() { + document.addEventListener('globalNotice', this.handleNoticeReceived); + }, + componentWillUnmount: function() { + document.removeEventListener('globalNotice', this.handleNoticeReceived); + }, + render: function() { + if (!this.state.message) { + return null; + } + + return ( + + {this.state.message} + + ); + }, +}); + +export default NotificationBar; \ No newline at end of file diff --git a/ui/scss/all.scss b/ui/scss/all.scss index 126ad742c..89d84058a 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -13,6 +13,7 @@ @import "component/_channel-indicator.scss"; @import "component/_notice.scss"; @import "component/_modal-page.scss"; +@import "component/_notification-bar.scss"; @import "page/_developer.scss"; @import "page/_watch.scss"; @import "page/_reward.scss"; diff --git a/ui/scss/component/_notice.scss b/ui/scss/component/_notice.scss index 2ae4f403e..b77ba2a5a 100644 --- a/ui/scss/component/_notice.scss +++ b/ui/scss/component/_notice.scss @@ -5,6 +5,10 @@ border: 1px solid #000; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); border-radius: 5px; + + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; } .notice--error { diff --git a/ui/scss/component/_notification-bar.scss b/ui/scss/component/_notification-bar.scss new file mode 100644 index 000000000..2f9959f94 --- /dev/null +++ b/ui/scss/component/_notification-bar.scss @@ -0,0 +1,6 @@ +@import "../global"; + +.notification-bar { + margin-top: 5px; + margin-right: 10px; +} From 647eb80eefbf2ca1b5ee483dd729aedb4cca923a Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 2 Apr 2017 06:05:34 -0400 Subject: [PATCH 18/51] Add wrapper code for creating rewards Calls the API and displays notices for success and errors --- ui/js/rewards.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 ui/js/rewards.js diff --git a/ui/js/rewards.js b/ui/js/rewards.js new file mode 100644 index 000000000..262183f04 --- /dev/null +++ b/ui/js/rewards.js @@ -0,0 +1,54 @@ +import lbry from './lbry.js'; +import lbryio from './lbryio.js'; + +const MESSAGES = { + new_developer: "Your reward has been confirmed for registering as a new developer.", + confirm_email: "Your reward has been confirmed for verifying your email address.", + first_publish: "Your reward has been confirmed for making your first publication.", +}; + +const rewards = {}; + +rewards.claimReward = function(type) { + console.log('top of claimReward') + return new Promise((resolve, reject) => { + console.log('top of promise body') + lbry.get_new_address().then((address) => { + console.log('top of get_new_address') + const params = { + reward_type: type, + wallet_address: address, + }; + lbryio.call('reward', 'new', params, 'post').then(({RewardAmount}) => { + const result = { + type: type, + amount: RewardAmount, + message: MESSAGES[type], + }; + + // Display global notice + document.dispatchEvent(new CustomEvent('globalNotice', { + detail: { + message: MESSAGES[type], + isError: false, + }, + })); + + // Add more events here to display other places + + resolve(result); + }, (error) => { + document.dispatchEvent(new CustomEvent('globalNotice', { + detail: { + message: `Failed to claim reward: ${error.message}`, + isError: true, + }, + })); + document.dispatchEvent(new CustomEvent('rewardFailed', error)); + reject(error); + }); + }); + }); +} + +export default rewards; \ No newline at end of file From e47f86bbfe347ffcd4e11862dc084c2372e4da75 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 2 Apr 2017 06:07:04 -0400 Subject: [PATCH 19/51] Add mock for reward/new action --- ui/js/lbryio.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 7c6771aac..a8156ce75 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -24,6 +24,14 @@ const mocks = { claimed: false, }, ]; + }, + 'reward.new': ({reward_type}) => { + return { + UserID: localStorage.getItem('accessToken'), + UserWalletID: 123, + RewardType: reward_type, + RewardAmount: 50, + }; } }; From 26aba5fb04ace3689e2d2669ce4d216e9a8961e5 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 2 Apr 2017 06:08:25 -0400 Subject: [PATCH 20/51] Random corrections and style fixes --- ui/js/component/notice.js | 4 ---- ui/js/lbryio.js | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/ui/js/component/notice.js b/ui/js/component/notice.js index a09238294..068b545b5 100644 --- a/ui/js/component/notice.js +++ b/ui/js/component/notice.js @@ -1,8 +1,4 @@ import React from 'react'; -import lbry from '../lbry.js'; -import {Link} from '../component/link.js'; -import {FileActions} from '../component/file-actions.js'; -import {Thumbnail, TruncatedText, CreditAmount} from '../component/common.js'; export const Notice = React.createClass({ propTypes: { diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index a8156ce75..0a1fd4b8f 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -5,7 +5,7 @@ const lbryio = {}; const CONNECTION_STRING = 'https://apidev.lbry.tech/'; const mocks = { - 'reward_type.get': (name) => { + 'reward_type.get': ({name}) => { return { name: 'link_github', title: 'Link your GitHub account', @@ -35,7 +35,7 @@ const mocks = { } }; -lbryio.call = function(resource, action, params, method='get') { +lbryio.call = function(resource, action, params={}, method='get') { return new Promise((resolve, reject) => { /* temp code for mocks */ if (`${resource}.${action}` in mocks) { From 326d06635393e60dcf56deac6c6a8da16d340fca Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 2 Apr 2017 06:10:12 -0400 Subject: [PATCH 21/51] Fixes to Email page Still not actually working (doing it in the welcome screen right now) --- ui/js/page/email.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/ui/js/page/email.js b/ui/js/page/email.js index 926e253d8..b7f31cd49 100644 --- a/ui/js/page/email.js +++ b/ui/js/page/email.js @@ -3,6 +3,7 @@ import lbryio from '../lbryio.js'; import {getLocal, setLocal} from '../utils.js'; import FormField from '../component/form.js' import {Link} from '../component/link.js' +import rewards from '../rewards.js'; const EmailPage = React.createClass({ handleSubmit: function(event) { @@ -14,12 +15,6 @@ const EmailPage = React.createClass({ this._emailField.warnRequired(); } }, - getInitialState: function() { - return { - rewardType: null, - email: null, - }; - }, componentWillMount: function() { this._getRewardType(); }, @@ -27,7 +22,7 @@ const EmailPage = React.createClass({ return (
-

Register a LBRY account

+

Verify your Email Address

From 498618e39b49844d1aea22370b0a54c496f75be2 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 2 Apr 2017 22:00:24 -0400 Subject: [PATCH 22/51] More revisions to Welcome screen - Eliminate intro page - Add "verify email" page - Move buttons into individual components - Claim reward (not handling reporting success/failure yet) --- ui/js/component/welcome.js | 113 +++++++++++++++---------------------- ui/js/lbryio.js | 8 --- 2 files changed, 47 insertions(+), 74 deletions(-) diff --git a/ui/js/component/welcome.js b/ui/js/component/welcome.js index 7694997c0..36036abe1 100644 --- a/ui/js/component/welcome.js +++ b/ui/js/component/welcome.js @@ -6,19 +6,6 @@ import {Link} from '../component/link.js'; import FormField from '../component/form.js'; import Notice from '../component/notice.js' -const IntroStage = React.createClass({ - componentWillMount: function() { - this.props.onCompleted(); // Nothing required to move on - }, - render: function() { - return ( -
-

Welcome to LBRY

-

Content will go here...

-
- ); - } -}); const SubmitEmailStage = React.createClass({ getInitialState: function() { @@ -26,6 +13,7 @@ const SubmitEmailStage = React.createClass({ rewardType: null, email: '', submitting: false, + errorMessage: null, }; }, handleEmailChanged: function(event) { @@ -40,84 +28,94 @@ const SubmitEmailStage = React.createClass({ submitting: true, }); lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => { - this.setState({ - submitting: false, - message: "Your email has been verified.", - success: true, - }); - this.props.onCompleted(); + this.props.onDone(); }, (error) => { this.setState({ submitting: false, - message: error.message, - success: false, + errorMessage: error.message, }); }); }, render: function() { return (
-

Verify Your Email Address

- {this.state.message - ? - {this.state.message} +

Welcome to LBRY

+ {this.state.errorMessage + ? + {this.state.errorMessage} : null}

Copy here explaining what we do with your email, and the reward.

-
-
+
Email
+
); } }); -/* const ConfirmEmailStage = React.createClass({ +const ConfirmEmailStage = React.createClass({ getInitialState: function() { return { rewardType: null, - email: '', + code: '', submitting: false, + errorMessage: null, }; }, - handleEmailChanged: function(event) { + handleCodeChanged: function(event) { this.setState({ - email: event.target.value, + code: event.target.value, }); }, handleSubmit: function(event) { event.preventDefault(); - // ... + this.setState({ + submitting: true, + }); + + lbryio.call('user_email', 'confirm', {verification_token: this.state.code}, 'post').then(() => { + rewards.claimReward('confirm_email').then(() => { + console.log('succeeded'); + this.props.onDone(); + }, (err) => { + console.log('failed'); + this.props.onDone(); + }); + }, (error) => { + this.setState({ + submitting: false, + errorMessage: error.message, + }); + }); }, render: function() { return (

Confirm Your Email Address

- {this.state.message - ? - {this.state.message} + {this.state.errorMessage + ? + {this.state.errorMessage} : null} -

Ask the user to take steps needed to confirm (click link in confirmation email, etc.)

+

Please enter your verification code to confirm your email address.

-
-
+
+
); } -}); */ +}); const FinalMessageStage = React.createClass({ - componentWillMount: function() { - this.props.onCompleted(); - }, render: function() { return (

Email verified

Text here about what happens next

+
); } @@ -125,9 +123,8 @@ const FinalMessageStage = React.createClass({ export const Welcome = React.createClass({ _stages: [ - IntroStage, SubmitEmailStage, - //ConfirmEmailStage, + ConfirmEmailStage, FinalMessageStage, ], propTypes: { @@ -138,36 +135,20 @@ export const Welcome = React.createClass({ stageNum: 0, }; }, - handleNextClicked: function() { + handleStageDone: function() { if (this.state.stageNum >= this._stages.length - 1) { this.props.onDone(); + } else { + this.setState({ + stageNum: this.state.stageNum + 1, + }); } - - this.setState({ - stageNum: this.state.stageNum + 1, - stageCompleted: false, - }); - }, - handleDoneClicked: function() { - this.props.onDone(); - }, - handleStageComplete: function() { - console.log('inside handleStageComplete') - this.setState({ - stageCompleted: true, - }); }, render: function() { const Content = this._stages[this.state.stageNum]; - const isLastStage = this.state.stageNum >= this._stages.length - 1; return ( - -
- {!isLastStage - ? - : } -
+
); } diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 0a1fd4b8f..e4bb40eb4 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -25,14 +25,6 @@ const mocks = { }, ]; }, - 'reward.new': ({reward_type}) => { - return { - UserID: localStorage.getItem('accessToken'), - UserWalletID: 123, - RewardType: reward_type, - RewardAmount: 50, - }; - } }; lbryio.call = function(resource, action, params={}, method='get') { From 575db85477eadddd472622e759bcfef64c378feb Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 9 Apr 2017 11:06:23 -0400 Subject: [PATCH 23/51] so far --- .gitignore | 1 + app/main.js | 4 +- doitagain.sh | 4 + ui/js/app.js | 49 +------ ui/js/component/auth.js | 196 ++++++++++++++++++++++++++++ ui/js/component/form.js | 91 +++++++------ ui/js/component/splash.js | 29 ++-- ui/js/lbry.js | 54 ++++---- ui/js/lbryio.js | 91 +++++++++++-- ui/js/main.js | 37 +++--- ui/js/page/discover.js | 4 +- ui/js/page/publish.js | 2 +- ui/js/rewards.js | 5 +- ui/js/utils.js | 2 +- ui/scss/_form.scss | 66 ++++++++++ ui/scss/_global.scss | 8 +- ui/scss/_gui.scss | 175 +------------------------ ui/scss/_reset.scss | 19 +-- ui/scss/all.scss | 3 + ui/scss/component/_button.scss | 78 +++++++++++ ui/scss/component/_form-field.scss | 36 +++++ ui/scss/component/_load-screen.scss | 2 +- ui/scss/component/_modal-page.scss | 28 ++++ 23 files changed, 625 insertions(+), 359 deletions(-) create mode 100755 doitagain.sh create mode 100644 ui/js/component/auth.js create mode 100644 ui/scss/_form.scss create mode 100644 ui/scss/component/_button.scss create mode 100644 ui/scss/component/_form-field.scss diff --git a/.gitignore b/.gitignore index ccfec0983..d1b68b9b7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ dist /app/node_modules /build/venv /lbry-app-venv +/lbry-venv /daemon/build /daemon/venv /daemon/requirements.txt diff --git a/app/main.js b/app/main.js index 23239145b..e2fad0905 100644 --- a/app/main.js +++ b/app/main.js @@ -62,9 +62,9 @@ function getPidsForProcessName(name) { } function createWindow () { - win = new BrowserWindow({backgroundColor: '#155B4A'}) //$color-primary + win = new BrowserWindow({backgroundColor: '#155B4A', minWidth: 800, minHeight: 1000 }) //$color-primary win.maximize() - //win.webContents.openDevTools() + win.webContents.openDevTools(); win.loadURL(`file://${__dirname}/dist/index.html`) win.on('closed', () => { win = null diff --git a/doitagain.sh b/doitagain.sh new file mode 100755 index 000000000..37564e1dd --- /dev/null +++ b/doitagain.sh @@ -0,0 +1,4 @@ +#!/bin/bash +rm -rf ~/.lbrynet/ +rm -rf ~/.lbryum/ +./node_modules/.bin/electron app diff --git a/ui/js/app.js b/ui/js/app.js index 5572550ff..03d508f81 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -47,12 +47,6 @@ var App = React.createClass({ _upgradeDownloadItem: null, _isMounted: false, _version: null, - - getDefaultProps: function() { - return { - address: window.location.search - }; - }, getUpdateUrl: function() { switch (process.platform) { case 'darwin': @@ -87,54 +81,19 @@ var App = React.createClass({ pageArgs: pageArgs === undefined ? null : pageArgs }; }, - updateRegistrationStatus: function() { - if (localStorage.getItem('accessToken')) { - this.setState({ - registrationCheckComplete: true, - }); - } else { - lbry.status().then(({installation_id}) => { - installation_id += parseInt(Date.now(), 10); // temp - installation_id += "X".repeat(96 - installation_id.length); // temp - lbryio.call('user_install', 'exists', {app_id: installation_id}).then((userExists) => { - // TODO: deal with case where user exists already with the same app ID, but we have no access token. - // Possibly merge in to the existing user with the same app ID. - lbryio.call('user', 'new', { - language: 'en', - app_id: installation_id, - }, 'post').then(({ID}) => { - localStorage.setItem('accessToken', ID); - localStorage.setItem('appId', installation_id); - this.setState({ - registrationCheckComplete: true, - justRegistered: true, - }); - }); - }) - }); - } - }, getInitialState: function() { var match, param, val, viewingPage, pageArgs, drawerOpenRaw = sessionStorage.getItem('drawerOpen'); - return Object.assign(this.getViewingPageAndArgs(this.props.address), { + return Object.assign(this.getViewingPageAndArgs(window.location.search), { drawerOpen: drawerOpenRaw !== null ? JSON.parse(drawerOpenRaw) : true, errorInfo: null, modal: null, downloadProgress: null, downloadComplete: false, - registrationCheckComplete: null, - justRegistered: false, }); }, componentWillMount: function() { - if (!localStorage.getItem('accessToken') && window.location.search != '?discover') { - // User isn't registered but somehow made it to a page other than Discover, so send them to - // Discover to get them registered and show them the welcome screen. - window.location.search = '?discover'; - } - document.addEventListener('unhandledError', (event) => { this.alertError(event.detail); }); @@ -172,8 +131,6 @@ var App = React.createClass({ }); }); } - - this.updateRegistrationStatus(); }, openDrawer: function() { sessionStorage.setItem('drawerOpen', true); @@ -337,10 +294,6 @@ var App = React.createClass({ } }, render: function() { - if (!this.state.registrationCheckComplete) { - return null; - } - var mainContent = this.getMainContent(), headerLinks = this.getHeaderLinks(), searchQuery = this.state.viewingPage == 'discover' && this.state.pageArgs ? this.state.pageArgs : ''; diff --git a/ui/js/component/auth.js b/ui/js/component/auth.js new file mode 100644 index 000000000..2b7e6d02b --- /dev/null +++ b/ui/js/component/auth.js @@ -0,0 +1,196 @@ +import React from 'react'; +import lbryio from '../lbryio.js'; + +import ModalPage from './modal-page.js'; +import {Link} from '../component/link.js'; +import FormField from '../component/form.js'; +import Notice from '../component/notice.js' + + +const SubmitEmailStage = React.createClass({ + getInitialState: function() { + return { + rewardType: null, + email: '', + submitting: false + }; + }, + handleEmailChanged: function(event) { + this.setState({ + email: event.target.value, + }); + }, + handleSubmit: function(event) { + event.preventDefault(); + + this.setState({ + submitting: true, + }); + lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => { + this.props.onEmailSaved(); + }, (error) => { + if (this._emailField) { + this._emailField.showError(error.message) + } + this.setState({ submitting: false }); + }); + }, + render: function() { + return ( +
+
+ { this._emailField = field }} type="text" label="Email" placeholder="webmaster@toplbryfan.com" + name="email" value={this.state.email} + onChange={this.handleEmailChanged} /> +
+ +
+ +
+ ); + } +}); + +const ConfirmEmailStage = React.createClass({ + getInitialState: function() { + return { + rewardType: null, + code: '', + submitting: false, + errorMessage: null, + }; + }, + handleCodeChanged: function(event) { + this.setState({ + code: event.target.value, + }); + }, + handleSubmit: function(event) { + event.preventDefault(); + this.setState({ + submitting: true, + }); + + lbryio.call('user_email', 'confirm', {verification_token: this.state.code}, 'post').then(() => { + rewards.claimReward('confirm_email').then(() => { + this.props.onDone(); + }, (err) => {l + this.props.onDone(); + }); + }, (error) => { + if (this._codeField) { + this._codeField.showError(error.message) + this.setState({ submitting: false }) + } + }); + }, + render: function() { + return ( +
+
+ { this._codeField = field }} type="text" + name="code" placeholder="a94bXXXXXXXXXXXXXX" value={this.state.code} onChange={this.handleCodeChanged} + helper="A verification code is required to access this version."/> +
+ +
+ +
+ ); + } +}); + +const ErrorStage = React.createClass({ + render: function() { + //
+ return ( +
+

An error was encountered that we cannot continue from.

+

At least we're earning the name beta.

+ { window.location.reload() } } /> +
+ ); + } +}); + +const PendingStage = React.createClass({ + render: function() { + return ( +
+

Preparing for first access

+
+ ); + } +}); + +export const AuthOverlay = React.createClass({ + _stages: { + pending: PendingStage, + error: ErrorStage, + email: SubmitEmailStage, + confirm: ConfirmEmailStage, + }, + propTypes: { + // onDone: React.PropTypes.func.isRequired, + }, + getInitialState: function() { + return { + stage: "pending", + stageProps: {} + }; + }, + componentWillMount: function() { + lbryio.authenticate().then(function(user) { + console.log(user); + if (!user.HasVerifiedEmail) { + this.setState({ + stage: "email", + stageProps: { + onEmailSaved: function() { + this.setState({ + stage: "confirm" + }) + }.bind(this) + } + }) + } else { + this.setState({ stage: null }) + } + }.bind(this)).catch((err) => { + this.setState({ + stage: "error", + stageProps: { errorText: err.message } + }) + document.dispatchEvent(new CustomEvent('unhandledError', { + detail: { + message: err.message, + data: err.stack + } + })); + }) + }, + // handleStageDone: function() { + // if (this.state.stageNum >= this._stages.length - 1) { + // this.props.onDone(); + // } else { + // this.setState({ + // stageNum: this.state.stageNum + 1, + // }); + // } + // }, + + // + render: function() { + console.log(lbryio.user); + if (!this.state.stage || lbryio.user && lbryio.user.HasVerifiedEmail) { + return null; + } + const StageContent = this._stages[this.state.stage]; + return ( + +

LBRY Early Access

+ +
+ ); + } +}); \ No newline at end of file diff --git a/ui/js/component/form.js b/ui/js/component/form.js index 33e4aee66..176f3334f 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -6,6 +6,8 @@ var requiredFieldWarningStyle = { transition: 'opacity 400ms ease-in', }; +var formFieldCounter = 0; + var FormField = React.createClass({ _fieldRequiredText: 'This field is required', _type: null, @@ -13,11 +15,12 @@ var FormField = React.createClass({ propTypes: { type: React.PropTypes.string.isRequired, + row: React.PropTypes.bool, hidden: React.PropTypes.bool, }, getInitialState: function() { return { - adviceState: 'hidden', + errorState: 'hidden', adviceText: null, } }, @@ -33,25 +36,25 @@ var FormField = React.createClass({ this._element = this.props.type; } }, - showAdvice: function(text) { + showError: function(text) { this.setState({ - adviceState: 'shown', + errorState: 'shown', adviceText: text, }); - setTimeout(() => { - this.setState({ - adviceState: 'fading', - }); - setTimeout(() => { - this.setState({ - adviceState: 'hidden', - }); - }, 450); - }, 5000); + // setTimeout(() => { + // this.setState({ + // errorState: 'fading', + // }); + // setTimeout(() => { + // this.setState({ + // errorState: 'hidden', + // }); + // }, 450); + // }, 5000); }, warnRequired: function() { - this.showAdvice(this._fieldRequiredText); + this.showError(this._fieldRequiredText); }, focus: function() { this.refs.field.focus(); @@ -70,43 +73,39 @@ var FormField = React.createClass({ }, render: function() { // Pass all unhandled props to the field element - const otherProps = Object.assign({}, this.props); + const otherProps = Object.assign({}, this.props), + hasError = this.state.errorState != 'hidden'; delete otherProps.type; delete otherProps.hidden; + delete otherProps.label; + delete otherProps.row; + delete otherProps.helper; - return ( - !this.props.hidden - ?
- - {this.props.children} - - {this.state.adviceText} -
- : null - ); - } -}); + ++formFieldCounter; + const elementId = "form-field-" + formFieldCounter -var FormFieldAdvice = React.createClass({ - propTypes: { - state: React.PropTypes.string.isRequired, - }, - render: function() { + if (this.props.hidden) { + return null; + } + + const field =
+ { this.props.label ? +
+ +
: '' + } + + {this.props.children} + + { !hasError && this.props.helper ?
{this.props.helper}
: '' } + { hasError ?
{this.state.adviceText}
: '' } +
return ( - this.props.state != 'hidden' - ?
-
- -
- - {this.props.children} - -
-
-
- : null + this.props.row ? +
{field}
: + field ); } }); diff --git a/ui/js/component/splash.js b/ui/js/component/splash.js index 8de7e1bbf..a156718b4 100644 --- a/ui/js/component/splash.js +++ b/ui/js/component/splash.js @@ -13,11 +13,12 @@ var SplashScreen = React.createClass({ isLagging: false, } }, - updateStatus: function(was_lagging=false) { - lbry.getDaemonStatus(this._updateStatusCallback); + updateStatus: function() { + lbry.status().then(this._updateStatusCallback); }, _updateStatusCallback: function(status) { - if (status.code == 'started') { + const startupStatus = status.startup_status + if (startupStatus.code == 'started') { // Wait until we are able to resolve a name before declaring // that we are done. // TODO: This is a hack, and the logic should live in the daemon @@ -34,20 +35,28 @@ var SplashScreen = React.createClass({ return; } this.setState({ - details: status.message + (status.is_lagging ? '' : '...'), - isLagging: status.is_lagging, + details: startupStatus.message + (startupStatus.is_lagging ? '' : '...'), + isLagging: startupStatus.is_lagging, }); setTimeout(() => { - this.updateStatus(status.is_lagging); + this.updateStatus(); }, 500); }, componentDidMount: function() { - lbry.connect((connected) => { - this.updateStatus(); - }); + lbry.connect().then((isConnected) => { + if (isConnected) { + this.updateStatus(); + } else { + this.setState({ + isLagging: true, + message: "Failed to connect to LBRY", + details: "LBRY was unable to start and connect properly." + }) + } + }) }, render: function() { - return ; + return } }); diff --git a/ui/js/lbry.js b/ui/js/lbry.js index f2f201de6..4d227248e 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -118,28 +118,36 @@ lbry.call = function (method, params, callback, errorCallback, connectFailedCall //core -lbry.connect = function(callback) -{ - // Check every half second to see if the daemon is accepting connections - // Once this returns True, can call getDaemonStatus to see where - // we are in the startup process - function checkDaemonStarted(tryNum=0) { - lbry.isDaemonAcceptingConnections(function (runningStatus) { - if (runningStatus) { - lbry.isConnected = true; - callback(true); - } else { - if (tryNum <= 600) { // Move # of tries into constant or config option - setTimeout(function () { - checkDaemonStarted(tryNum + 1); - }, 500); - } else { - callback(false); - } +lbry._connectPromise = null; +lbry.connect = function() { + if (lbry._connectPromise === null) { + + lbry._connectPromise = new Promise((resolve, reject) => { + + // Check every half second to see if the daemon is accepting connections + function checkDaemonStarted(tryNum = 0) { + lbry.isDaemonAcceptingConnections(function (runningStatus) { + if (runningStatus) { + resolve(true); + } + else { + if (tryNum <= 600) { // Move # of tries into constant or config option + setTimeout(function () { + checkDaemonStarted(tryNum + 1); + }, tryNum < 100 ? 200 : 1000); + } + else { + reject(new Error("Unable to connect to LBRY")); + } + } + }); } + + checkDaemonStarted(); }); } - checkDaemonStarted(); + + return lbry._connectPromise; } lbry.isDaemonAcceptingConnections = function (callback) { @@ -147,10 +155,6 @@ lbry.isDaemonAcceptingConnections = function (callback) { lbry.call('status', {}, () => callback(true), null, () => callback(false)) }; -lbry.getDaemonStatus = function (callback) { - lbry.call('daemon_status', {}, callback); -}; - lbry.checkFirstRun = function(callback) { lbry.call('is_first_run', {}, callback); } @@ -430,6 +434,10 @@ lbry.getClientSettings = function() { lbry.getClientSetting = function(setting) { var localStorageVal = localStorage.getItem('setting_' + setting); + if (setting == 'showDeveloperMenu') + { + return true; + } return (localStorageVal === null ? lbry.defaultClientSettings[setting] : JSON.parse(localStorageVal)); } diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index e4bb40eb4..582592b71 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -1,8 +1,15 @@ +import {getLocal, setLocal} from './utils.js'; +import lbry from './lbry.js'; + const querystring = require('querystring'); -const lbryio = {}; +const lbryio = { + _accessToken: getLocal('accessToken'), + _authenticationPromise: null, + _user : null +}; -const CONNECTION_STRING = 'https://apidev.lbry.tech/'; +const CONNECTION_STRING = 'http://localhost:8080/'; const mocks = { 'reward_type.get': ({name}) => { @@ -37,25 +44,18 @@ lbryio.call = function(resource, action, params={}, method='get') { /* end temp */ - console.log('about to create xhr object'); const xhr = new XMLHttpRequest; - xhr.addEventListener('error', function (error) { - console.log('received XHR error:', error); - reject(error); + xhr.addEventListener('error', function (event) { + reject(new Error("Something went wrong making an internal API call.")); }); - console.log('about to add timeout listener'); xhr.addEventListener('timeout', function() { - console.log('XHR timed out'); - reject(new Error('XMLHttpRequest connection timed out')); }); - console.log('about to create load listener'); xhr.addEventListener('load', function() { - console.log('loaded'); const response = JSON.parse(xhr.responseText); if (!response.success) { @@ -78,14 +78,12 @@ lbryio.call = function(resource, action, params={}, method='get') { } }); - console.log('about to call xhr.open'); - // For social media auth: //const accessToken = localStorage.getItem('accessToken'); //const fullParams = {...params, ... accessToken ? {access_token: accessToken} : {}}; // Temp app ID based auth: - const fullParams = {app_id: localStorage.getItem('appId'), ...params}; + const fullParams = {app_id: lbryio._accessToken, ...params}; if (method == 'get') { console.info('GET ', CONNECTION_STRING + resource + '/' + action, ' | params:', fullParams); @@ -100,4 +98,69 @@ lbryio.call = function(resource, action, params={}, method='get') { }); }; +lbryio.setAccessToken = (token) => { + setLocal('accessToken', token) + lbryio._accessToken = token +} + +lbryio.authenticate = () => { + if (lbryio._authenticationPromise === null) { + lbryio._authenticationPromise = new Promise((resolve, reject) => { + lbry.status().then(({installation_id}) => { + + //temp hack for installation_ids being wrong + installation_id += "Y".repeat(96 - installation_id.length) + + function setCurrentUser() { + lbryio.call('user', 'me').then((data) => { + lbryio.user = data + resolve(data) + }).catch(function(err) { + lbryio.setAccessToken(null); + reject(err); + }) + } + + if (!lbryio._accessToken) { + lbryio.call('user', 'new', { + language: 'en', + app_id: installation_id, + }, 'post').then(function(responseData) { + if (!responseData.ID) { + reject(new Error("Received invalid authentication response.")); + } + lbryio.setAccessToken(installation_id) + setCurrentUser() + }).catch(function(error) { + + /* + until we have better error code format, assume all errors are duplicate application id + if we're wrong, this will be caught by later attempts to make a valid call + */ + lbryio.setAccessToken(installation_id) + setCurrentUser() + }) + } else { + setCurrentUser() + } + // if (!lbryio._ + //(data) => { + // resolve(data) + // localStorage.setItem('accessToken', ID); + // localStorage.setItem('appId', installation_id); + // this.setState({ + // registrationCheckComplete: true, + // justRegistered: true, + // }); + //}); + // lbryio.call('user_install', 'exists', {app_id: installation_id}).then((userExists) => { + // // TODO: deal with case where user exists already with the same app ID, but we have no access token. + // // Possibly merge in to the existing user with the same app ID. + // }) + }).catch(reject); + }); + } + return lbryio._authenticationPromise; +} + export default lbryio; diff --git a/ui/js/main.js b/ui/js/main.js index ea8d4d0a5..12dee5d92 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -5,6 +5,8 @@ import lbryio from './lbryio.js'; import lighthouse from './lighthouse.js'; import App from './app.js'; import SplashScreen from './component/splash.js'; +import {AuthOverlay} from './component/auth.js'; +import {Welcome} from './component/welcome.js'; const {remote} = require('electron'); const contextMenu = remote.require('./menu/context-menu'); @@ -20,30 +22,21 @@ window.addEventListener('contextmenu', (event) => { let init = function() { window.lbry = lbry; window.lighthouse = lighthouse; + let canvas = document.getElementById('canvas'); - var canvas = document.getElementById('canvas'); - if (window.sessionStorage.getItem('loaded') == 'y') { - ReactDOM.render(, canvas) + lbry.connect().then(function(isConnected) { + lbryio.authenticate() //start auth process as soon as soon as we can get an install ID + }) + + async function onDaemonReady() { + window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again + ReactDOM.render(
, canvas) + } + + if (window.sessionStorage.getItem('loaded') == 'y' && false) { + onDaemonReady(); } else { - ReactDOM.render( - ( - { - if (balance <= 0) { - window.location.href = '?claim'; - } else { - ReactDOM.render(, canvas); - } - }); - } else { - ReactDOM.render(, canvas); - } - }}/> - ), canvas); + ReactDOM.render(, canvas); } }; diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js index c6705d871..993481d07 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover.js @@ -5,7 +5,6 @@ import {FileTile} from '../component/file-tile.js'; import {Link} from '../component/link.js'; import {ToolTip} from '../component/tooltip.js'; import {BusyMessage} from '../component/common.js'; -import {Welcome} from '../component/welcome.js'; var fetchResultsStyle = { color: '#888', @@ -174,13 +173,12 @@ var DiscoverPage = React.createClass({ }, render: function() { + //{ !this.props.query && !this.state.searching ? : null } return (
{ this.state.searching ? : null } { !this.state.searching && this.props.query && this.state.results.length ? : null } { !this.state.searching && this.props.query && !this.state.results.length ? : null } - { !this.props.query && !this.state.searching ? : null } -
); } diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 82ef6bdf8..6e0799263 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -162,7 +162,7 @@ var PublishPage = React.createClass({ } if (!lbry.nameIsValid(rawName, false)) { - this.refs.name.showAdvice('LBRY names must contain only letters, numbers and dashes.'); + this.refs.name.showError('LBRY names must contain only letters, numbers and dashes.'); return; } diff --git a/ui/js/rewards.js b/ui/js/rewards.js index 262183f04..4310b18b8 100644 --- a/ui/js/rewards.js +++ b/ui/js/rewards.js @@ -10,7 +10,6 @@ const MESSAGES = { const rewards = {}; rewards.claimReward = function(type) { - console.log('top of claimReward') return new Promise((resolve, reject) => { console.log('top of promise body') lbry.get_new_address().then((address) => { @@ -30,7 +29,7 @@ rewards.claimReward = function(type) { document.dispatchEvent(new CustomEvent('globalNotice', { detail: { message: MESSAGES[type], - isError: false, + isError: false, }, })); @@ -41,7 +40,7 @@ rewards.claimReward = function(type) { document.dispatchEvent(new CustomEvent('globalNotice', { detail: { message: `Failed to claim reward: ${error.message}`, - isError: true, + isError: true, }, })); document.dispatchEvent(new CustomEvent('rewardFailed', error)); diff --git a/ui/js/utils.js b/ui/js/utils.js index 5b5cf246a..290a0f54e 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -12,4 +12,4 @@ export function getLocal(key) { */ export function setLocal(key, value) { localStorage.setItem(key, JSON.stringify(value)); -} +} \ No newline at end of file diff --git a/ui/scss/_form.scss b/ui/scss/_form.scss new file mode 100644 index 000000000..c54343c4a --- /dev/null +++ b/ui/scss/_form.scss @@ -0,0 +1,66 @@ +@import "global"; + +/* this probably shouldn't exist but it does so here we are - Jeremy */ + + +textarea, +select, +input[type="text"], +input[type="password"], +input[type="email"], +input[type="number"], +input[type="search"], +input[type="date"] { + @include placeholder { + color: lighten($color-text-dark, 60%); + } + transition: all $transition-standard; + cursor: pointer; + padding-left: 1px; + padding-right: 1px; + box-sizing: border-box; + -webkit-appearance: none; + &[readonly] { + background-color: #bbb; + } +} + +input[type="text"], +input[type="password"], +input[type="email"], +input[type="number"], +input[type="search"], +input[type="date"] { + border-bottom: 2px solid $color-form-border; + line-height: $spacing-vertical - 4; + height: $spacing-vertical * 1.5; + &.form-field__input--error { + border-color: $color-error; + } +} + +textarea:focus, +input[type="text"]:focus, +input[type="password"]:focus, +input[type="email"]:focus, +input[type="number"]:focus, +input[type="search"]:focus, +input[type="date"]:focus { + border-color: $color-primary; +} + +textarea { + border: 2px solid $color-form-border; +} + +.form-row +{ + + .form-row + { + margin-top: $spacing-vertical; + } + + .form-row-submit + { + margin-top: $spacing-vertical; + } +} \ No newline at end of file diff --git a/ui/scss/_global.scss b/ui/scss/_global.scss index 201409835..7d3249888 100644 --- a/ui/scss/_global.scss +++ b/ui/scss/_global.scss @@ -9,14 +9,15 @@ $color-primary: #155B4A; $color-light-alt: hsl(hue($color-primary), 15, 85); $color-text-dark: #000; $color-help: rgba(0,0,0,.6); -$color-notice: #921010; -$color-warning: #ffffff; +$color-notice: #8a6d3b; +$color-error: #a94442; $color-load-screen-text: #c3c3c3; $color-canvas: #f5f5f5; $color-bg: #ffffff; $color-bg-alt: #D9D9D9; $color-money: #216C2A; $color-meta-light: #505050; +$color-form-border: rgba(160,160,160,.5); $font-size: 16px; $font-line-height: 1.3333; @@ -29,6 +30,9 @@ $height-header: $spacing-vertical * 2.5; $height-button: $spacing-vertical * 1.5; $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); + +$transition-standard: .225s ease; $blur-intensity: 8px; diff --git a/ui/scss/_gui.scss b/ui/scss/_gui.scss index 1fb53790c..691ccb328 100644 --- a/ui/scss/_gui.scss +++ b/ui/scss/_gui.scss @@ -21,7 +21,7 @@ &:hover { opacity: $hover-opacity; - transition: opacity .225s ease; + transition: opacity $transition-standard; text-decoration: underline; .icon { text-decoration: none; @@ -76,11 +76,6 @@ sup, sub { sup { top: -0.4em; } sub { top: 0.4em; } -label { - cursor: default; - display: block; -} - code { font: 0.8em Consolas, 'Lucida Console', 'Source Sans', monospace; background-color: #eee; @@ -104,23 +99,6 @@ p opacity: 0.7; } -input[type="text"], input[type="search"], textarea -{ - @include placeholder { - color: lighten($color-text-dark, 60%); - } - border: 2px solid rgba(160,160,160,.5); - padding-left: 5px; - padding-right: 5px; - box-sizing: border-box; - -webkit-appearance: none; -} -input[type="text"], input[type="search"] -{ - line-height: $spacing-vertical - 4; - height: $spacing-vertical * 1.5; -} - .truncated-text { display: inline-block; } @@ -144,75 +122,6 @@ input[type="text"], input[type="search"] } } -.button-set-item { - position: relative; - display: inline-block; - - + .button-set-item - { - margin-left: $padding-button; - } -} - -.button-block, .faux-button-block -{ - display: inline-block; - height: $height-button; - line-height: $height-button; - text-decoration: none; - border: 0 none; - text-align: center; - border-radius: 2px; - text-transform: uppercase; - .icon - { - top: 0em; - } - .icon:first-child - { - padding-right: 5px; - } - .icon:last-child - { - padding-left: 5px; - } -} -.button-block -{ - cursor: pointer; -} - -.button__content { - margin: 0 $padding-button; -} - -.button-primary -{ - color: white; - background-color: $color-primary; - box-shadow: $default-box-shadow; -} -.button-alt -{ - background-color: $color-bg-alt; - box-shadow: $default-box-shadow; -} - -.button-text -{ - @include text-link(); - display: inline-block; - - .button__content { - margin: 0 $padding-text-link; - } -} -.button-text-help -{ - @include text-link(#aaa); - font-size: 0.8em; -} - .icon:only-child { position: relative; top: 0.16em; @@ -235,87 +144,6 @@ input[type="text"], input[type="search"] font-style: italic; } -.form-row -{ - + .form-row - { - margin-top: $spacing-vertical / 2; - } - .help - { - margin-top: $spacing-vertical / 2; - } - + .form-row-submit - { - margin-top: $spacing-vertical; - } -} - -.form-field-container { - display: inline-block; -} - -.form-field--text { - width: 330px; -} - -.form-field--text-number { - width: 50px; -} - -.form-field-advice-container { - position: relative; -} - -.form-field-advice { - position: absolute; - top: 0px; - left: 0px; - - display: flex; - flex-direction: column; - - white-space: nowrap; - - transition: opacity 400ms ease-in; -} - -.form-field-advice--fading { - opacity: 0; -} - -.form-field-advice__arrow { - text-align: left; - padding-left: 18px; - - font-size: 22px; - line-height: 0.3; - color: darken($color-primary, 5%); -} - - -.form-field-advice__content-container { - display: inline-block; -} - -.form-field-advice__content { - display: inline-block; - - padding: 5px; - border-radius: 2px; - - background-color: darken($color-primary, 5%); - color: #fff; -} - -.form-field-label { - width: 118px; - text-align: right; - vertical-align: top; - display: inline-block; -} - - .sort-section { display: block; margin-bottom: 5px; @@ -350,7 +178,6 @@ input[type="text"], input[type="search"] background: rgb(255, 255, 255); overflow: auto; border-radius: 4px; - outline: none; padding: 36px; max-width: 250px; } diff --git a/ui/scss/_reset.scss b/ui/scss/_reset.scss index 66d0b0f1e..8e0db2623 100644 --- a/ui/scss/_reset.scss +++ b/ui/scss/_reset.scss @@ -3,20 +3,20 @@ body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fiel margin:0; padding:0; } -input:focus, textarea:focus +:focus { - outline: 0; + outline: 0; } -table +table { border-collapse: collapse; border-spacing:0; } -fieldset, img, iframe +fieldset, img, iframe { border: 0; } -h1, h2, h3, h4, h5, h6 +h1, h2, h3, h4, h5, h6 { font-weight:normal; } @@ -25,11 +25,12 @@ ol, ul list-style-position: inside; > li { list-style-position: inside; } } -input, textarea, select +input, textarea, select { - font-family:inherit; - font-size:inherit; - font-weight:inherit; + font-family:inherit; + font-size:inherit; + font-weight:inherit; + border: 0 none; } img { width: auto\9; diff --git a/ui/scss/all.scss b/ui/scss/all.scss index 89d84058a..3c28c011c 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -1,12 +1,15 @@ @import "_reset"; @import "_grid"; @import "_icons"; +@import "_form"; @import "_mediaelement"; @import "_canvas"; @import "_gui"; @import "component/_table"; +@import "component/_button.scss"; @import "component/_file-actions.scss"; @import "component/_file-tile.scss"; +@import "component/_form-field.scss"; @import "component/_menu.scss"; @import "component/_tooltip.scss"; @import "component/_load-screen.scss"; diff --git a/ui/scss/component/_button.scss b/ui/scss/component/_button.scss new file mode 100644 index 000000000..e3c5fe8e8 --- /dev/null +++ b/ui/scss/component/_button.scss @@ -0,0 +1,78 @@ +@import "../global"; + +$button-focus-shift: 12%; + +.button-set-item { + position: relative; + display: inline-block; + + + .button-set-item + { + margin-left: $padding-button; + } +} + +.button-block, .faux-button-block +{ + display: inline-block; + height: $height-button; + line-height: $height-button; + text-decoration: none; + border: 0 none; + text-align: center; + border-radius: 2px; + text-transform: uppercase; + .icon + { + top: 0em; + } + .icon:first-child + { + padding-right: 5px; + } + .icon:last-child + { + padding-left: 5px; + } +} +.button-block +{ + cursor: pointer; +} + +.button__content { + margin: 0 $padding-button; +} + +.button-primary +{ + $color-button-text: white; + color: darken($color-button-text, $button-focus-shift * 0.5); + background-color: $color-primary; + box-shadow: $default-box-shadow; + &:focus { + color: $color-button-text; + //box-shadow: $focus-box-shadow; + background-color: mix(black, $color-primary, $button-focus-shift) + } +} +.button-alt +{ + background-color: $color-bg-alt; + box-shadow: $default-box-shadow; +} + +.button-text +{ + @include text-link(); + display: inline-block; + + .button__content { + margin: 0 $padding-text-link; + } +} +.button-text-help +{ + @include text-link(#aaa); + font-size: 0.8em; +} diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss new file mode 100644 index 000000000..db2d168e0 --- /dev/null +++ b/ui/scss/component/_form-field.scss @@ -0,0 +1,36 @@ +@import "../global"; + +.form-field { + display: inline-block; +} + +.form-field__label { + margin-top: $spacing-vertical * 2/3; + margin-bottom: $spacing-vertical * 1/3; + line-height: 1; +} + +.form-field__label--error { + color: $color-error; +} + +.form-field__input-text { + width: 330px; +} + +.form-field__input-text-number { + width: 50px; +} + +.form-field__error, .form-field__helper { + margin-top: $spacing-vertical * 1/3; + font-size: 0.8em; + transition: opacity $transition-standard; +} + +.form-field__error { + color: $color-error; +} +.form-field__helper { + color: $color-help; +} \ No newline at end of file diff --git a/ui/scss/component/_load-screen.scss b/ui/scss/component/_load-screen.scss index e56eb12c0..0caa74f65 100644 --- a/ui/scss/component/_load-screen.scss +++ b/ui/scss/component/_load-screen.scss @@ -23,7 +23,7 @@ } .load-screen__details--warning { - color: $color-warning; + color: white; } .load-screen__cancel-link { diff --git a/ui/scss/component/_modal-page.scss b/ui/scss/component/_modal-page.scss index 1acc41790..5c56e1ad3 100644 --- a/ui/scss/component/_modal-page.scss +++ b/ui/scss/component/_modal-page.scss @@ -12,6 +12,24 @@ z-index: 9999; } +.modal-page { + position: fixed; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + left: 0; + right: 0; + top: 0; + bottom: 0; + + border: 1px solid rgb(204, 204, 204); + background: rgb(255, 255, 255); + overflow: auto; +} + +/* .modal-page { position: fixed; display: flex; @@ -31,3 +49,13 @@ right: 25px; bottom: 25px; } +*/ + +.modal-page__content { + h1, h2 { + margin-bottom: $spacing-vertical / 2; + } + h3, h4 { + margin-bottom: $spacing-vertical / 4; + } +} \ No newline at end of file From ecf54f400b454204392fd28dabe147d5890e14cb Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Mon, 10 Apr 2017 08:32:40 -0400 Subject: [PATCH 24/51] mostly garbage --- ui/js/app.js | 16 +- ui/js/component/auth.js | 161 ++++++++------- ui/js/component/drawer.js | 2 +- ui/js/component/file-actions.js | 4 +- ui/js/component/file-tile.js | 141 +++++++++++-- ui/js/component/form.js | 133 ++++++++----- ui/js/component/header.js | 4 +- ui/js/component/link.js | 74 +++++++ ui/js/component/notification-bar.js | 53 ----- ui/js/component/snack-bar.js | 58 ++++++ ui/js/component/welcome.js | 156 --------------- ui/js/lbry.js | 23 --- ui/js/lbryio.js | 24 +-- ui/js/main.js | 8 +- ui/js/page/claim_code.js | 158 --------------- ui/js/page/developer.js | 2 +- ui/js/page/discover.js | 56 +++--- ui/js/page/email.js | 4 +- ui/js/page/file-list.js | 2 +- ui/js/page/publish.js | 24 ++- ui/js/page/referral.js | 130 ------------ ui/js/page/reward.js | 242 +++++++++++------------ ui/js/page/rewards.js | 54 ++--- ui/js/page/settings.js | 149 ++++++++------ ui/js/page/wallet.js | 110 ++++++----- ui/js/rewards.js | 41 ++-- ui/js/utils.js | 16 ++ ui/scss/_canvas.scss | 83 ++------ ui/scss/_form.scss | 66 ------- ui/scss/_global.scss | 4 +- ui/scss/_gui.scss | 6 +- ui/scss/_reset.scss | 4 + ui/scss/all.scss | 4 +- ui/scss/component/_card.scss | 122 ++++++++++++ ui/scss/component/_file-tile.scss | 10 +- ui/scss/component/_form-field.scss | 84 +++++++- ui/scss/component/_modal-page.scss | 10 +- ui/scss/component/_notification-bar.scss | 6 - ui/scss/component/_snack-bar.scss | 42 ++++ 39 files changed, 1120 insertions(+), 1166 deletions(-) delete mode 100644 ui/js/component/notification-bar.js create mode 100644 ui/js/component/snack-bar.js delete mode 100644 ui/js/component/welcome.js delete mode 100644 ui/js/page/claim_code.js delete mode 100644 ui/js/page/referral.js delete mode 100644 ui/scss/_form.scss create mode 100644 ui/scss/component/_card.scss delete mode 100644 ui/scss/component/_notification-bar.scss create mode 100644 ui/scss/component/_snack-bar.scss diff --git a/ui/js/app.js b/ui/js/app.js index 03d508f81..424fb8533 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -2,22 +2,18 @@ import React from 'react'; import {Line} from 'rc-progress'; import lbry from './lbry.js'; -import lbryio from './lbryio.js'; import EmailPage from './page/email.js'; import SettingsPage from './page/settings.js'; import HelpPage from './page/help.js'; import WatchPage from './page/watch.js'; import ReportPage from './page/report.js'; import StartPage from './page/start.js'; -import ClaimCodePage from './page/claim_code.js'; -import ReferralPage from './page/referral.js'; import RewardsPage from './page/rewards.js'; import RewardPage from './page/reward.js'; import WalletPage from './page/wallet.js'; import ShowPage from './page/show.js'; import PublishPage from './page/publish.js'; import DiscoverPage from './page/discover.js'; -import SplashScreen from './component/splash.js'; import DeveloperPage from './page/developer.js'; import {FileListDownloaded, FileListPublished} from './page/file-list.js'; import Drawer from './component/drawer.js'; @@ -229,15 +225,11 @@ var App = React.createClass({ case 'wallet': case 'send': case 'receive': - case 'claim': - case 'referral': case 'rewards': return { '?wallet': 'Overview', '?send': 'Send', '?receive': 'Receive', - '?claim': 'Claim Beta Code', - '?referral': 'Check Referral Credit', '?rewards': 'Rewards', }; case 'downloaded': @@ -268,14 +260,8 @@ var App = React.createClass({ return ; case 'start': return ; - case 'claim': - return ; - case 'referral': - return ; case 'rewards': - return ; - case 'reward': - return ; + return ; case 'wallet': case 'send': case 'receive': diff --git a/ui/js/component/auth.js b/ui/js/component/auth.js index 2b7e6d02b..83edd2c9f 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth.js @@ -1,10 +1,11 @@ import React from 'react'; import lbryio from '../lbryio.js'; +import Modal from './modal.js'; import ModalPage from './modal-page.js'; -import {Link} from '../component/link.js'; -import FormField from '../component/form.js'; -import Notice from '../component/notice.js' +import {Link, RewardLink} from '../component/link.js'; +import {FormField, FormRow} from '../component/form.js'; +import rewards from '../rewards.js'; const SubmitEmailStage = React.createClass({ @@ -29,8 +30,8 @@ const SubmitEmailStage = React.createClass({ lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => { this.props.onEmailSaved(); }, (error) => { - if (this._emailField) { - this._emailField.showError(error.message) + if (this._emailRow) { + this._emailRow.showError(error.message) } this.setState({ submitting: false }); }); @@ -39,10 +40,10 @@ const SubmitEmailStage = React.createClass({ return (
- { this._emailField = field }} type="text" label="Email" placeholder="webmaster@toplbryfan.com" + { this._emailRow = ref }} type="text" label="Email" placeholder="webmaster@toplbryfan.com" name="email" value={this.state.email} onChange={this.handleEmailChanged} /> -
+
@@ -71,27 +72,29 @@ const ConfirmEmailStage = React.createClass({ submitting: true, }); - lbryio.call('user_email', 'confirm', {verification_token: this.state.code}, 'post').then(() => { - rewards.claimReward('confirm_email').then(() => { - this.props.onDone(); - }, (err) => {l - this.props.onDone(); - }); - }, (error) => { - if (this._codeField) { - this._codeField.showError(error.message) - this.setState({ submitting: false }) + const onSubmitError = function(error) { + if (this._codeRow) { + this._codeRow.showError(error.message) } - }); + this.setState({ submitting: false }); + }.bind(this) + + lbryio.call('user_email', 'confirm', {verification_token: this.state.code}, 'post').then((userEmail) => { + if (userEmail.IsVerified) { + this.props.onEmailConfirmed(); + } else { + onSubmitError(new Error("Your email is still not verified.")) //shouldn't happen? + } + }, onSubmitError); }, render: function() { return (
- { this._codeField = field }} type="text" + { this._codeRow = ref }} type="text" name="code" placeholder="a94bXXXXXXXXXXXXXX" value={this.state.code} onChange={this.handleCodeChanged} helper="A verification code is required to access this version."/> -
+
@@ -100,9 +103,29 @@ const ConfirmEmailStage = React.createClass({ } }); +const WelcomeStage = React.createClass({ + onRewardClaim: function() { + console.log('omg'); + }, + render: function() { + return ( +
+

Welcome to LBRY.

+

LBRY is the first community controlled content marketplace.

+

Since you're new here, we'll toss you some credits.

+
+ +
+

LBC is blah blah blah.

+

And remember, LBRY is a beta and be safe!

+
+ ); + } +}); + + const ErrorStage = React.createClass({ render: function() { - //
return (

An error was encountered that we cannot continue from.

@@ -129,68 +152,66 @@ export const AuthOverlay = React.createClass({ error: ErrorStage, email: SubmitEmailStage, confirm: ConfirmEmailStage, + welcome: WelcomeStage, }, - propTypes: { - // onDone: React.PropTypes.func.isRequired, - }, + getInitialState: function() { return { - stage: "pending", + stage: "welcome", stageProps: {} }; }, - componentWillMount: function() { - lbryio.authenticate().then(function(user) { - console.log(user); - if (!user.HasVerifiedEmail) { - this.setState({ - stage: "email", - stageProps: { - onEmailSaved: function() { - this.setState({ - stage: "confirm" - }) - }.bind(this) - } - }) - } else { - this.setState({ stage: null }) - } - }.bind(this)).catch((err) => { - this.setState({ - stage: "error", - stageProps: { errorText: err.message } - }) - document.dispatchEvent(new CustomEvent('unhandledError', { - detail: { - message: err.message, - data: err.stack - } - })); - }) + endAuth: function() { + this.setState({ + stage: null + }); + }, + componentWillMount: function() { + // lbryio.authenticate().then(function(user) { + // if (!user.HasVerifiedEmail) { //oops I fucked this up + // this.setState({ + // stage: "email", + // stageProps: { + // onEmailSaved: function() { + // this.setState({ + // stage: "confirm", + // stageProps: { + // onEmailConfirmed: function() { this.setState({ stage: "welcome"}) }.bind(this) + // } + // }) + // }.bind(this) + // } + // }) + // } else { + // this.endAuth() + // } + // }.bind(this)).catch((err) => { + // this.setState({ + // stage: "error", + // stageProps: { errorText: err.message } + // }) + // document.dispatchEvent(new CustomEvent('unhandledError', { + // detail: { + // message: err.message, + // data: err.stack + // } + // })); + // }) }, - // handleStageDone: function() { - // if (this.state.stageNum >= this._stages.length - 1) { - // this.props.onDone(); - // } else { - // this.setState({ - // stageNum: this.state.stageNum + 1, - // }); - // } - // }, - - // render: function() { - console.log(lbryio.user); if (!this.state.stage || lbryio.user && lbryio.user.HasVerifiedEmail) { return null; } const StageContent = this._stages[this.state.stage]; return ( - -

LBRY Early Access

- -
+ this.state.stage != "welcome" ? + +

LBRY Early Access

+ +
: + + + ); } }); \ No newline at end of file diff --git a/ui/js/component/drawer.js b/ui/js/component/drawer.js index eaf11506b..e719af073 100644 --- a/ui/js/component/drawer.js +++ b/ui/js/component/drawer.js @@ -55,7 +55,7 @@ var Drawer = React.createClass({ - + diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index 1a535099a..715133bd8 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -3,7 +3,7 @@ import lbry from '../lbry.js'; import {Link} from '../component/link.js'; import {Icon} from '../component/common.js'; import Modal from './modal.js'; -import FormField from './form.js'; +import {FormField} from './form.js'; import {ToolTip} from '../component/tooltip.js'; import {DropDownMenu, DropDownMenuItem} from './menu.js'; @@ -53,7 +53,7 @@ let WatchLink = React.createClass({ render: function() { return (
- + You don't have enough LBRY credits to pay for this stream. diff --git a/ui/js/component/file-tile.js b/ui/js/component/file-tile.js index b65434bac..bb9483374 100644 --- a/ui/js/component/file-tile.js +++ b/ui/js/component/file-tile.js @@ -47,7 +47,7 @@ let FilePrice = React.createClass({ } return ( - + ); @@ -131,8 +131,8 @@ export let FileTileStream = React.createClass({ const title = isConfirmed ? metadata.title : lbryUri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; return ( -
-
+
+
@@ -140,24 +140,139 @@ export let FileTileStream = React.createClass({ { !this.props.hidePrice ? : null} - -

+ +
+ +
+
+

+ + {isConfirmed + ? metadata.description + : This file is pending confirmation.} + +

+
+

+
+ {this.state.showNsfwHelp + ?
+

+ This content is Not Safe For Work. + To view adult content, please change your . +

+
+ : null} +
+ ); + } +}); + +export let FileCardStream = React.createClass({ + _fileInfoSubscribeId: null, + _isMounted: null, + + propTypes: { + metadata: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]), + outpoint: React.PropTypes.string, + hideOnRemove: React.PropTypes.bool, + hidePrice: React.PropTypes.bool, + obscureNsfw: React.PropTypes.bool + }, + getInitialState: function() { + return { + showNsfwHelp: false, + isHidden: false, + available: null, + } + }, + getDefaultProps: function() { + return { + obscureNsfw: !lbry.getClientSetting('showNsfw'), + hidePrice: false + } + }, + componentDidMount: function() { + this._isMounted = true; + if (this.props.hideOnRemove) { + this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); + } + }, + componentWillUnmount: function() { + if (this._fileInfoSubscribeId) { + lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); + } + }, + onFileInfoUpdate: function(fileInfo) { + if (!fileInfo && this._isMounted && this.props.hideOnRemove) { + this.setState({ + isHidden: true + }); + } + }, + handleMouseOver: function() { + if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { + this.setState({ + showNsfwHelp: true, + }); + } + }, + handleMouseOut: function() { + if (this.state.showNsfwHelp) { + this.setState({ + showNsfwHelp: false, + }); + } + }, + render: function() { + if (this.state.isHidden) { + return null; + } + + const lbryUri = uri.normalizeLbryUri(this.props.uri); + const metadata = this.props.metadata; + const isConfirmed = typeof metadata == 'object'; + const title = isConfirmed ? metadata.title : lbryUri; + const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; + return ( +
+
+ - -

- + hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} /> + +

+ +
+ { !this.props.hidePrice + ? + : null} +
+ {isConfirmed - ? metadata.description - : This file is pending confirmation.} + ? metadata.description + : This file is pending confirmation.} -

+
+
+
{this.state.showNsfwHelp diff --git a/ui/js/component/form.js b/ui/js/component/form.js index 176f3334f..b33fcddd5 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -1,31 +1,30 @@ import React from 'react'; import {Icon} from './common.js'; -var requiredFieldWarningStyle = { - color: '#cc0000', - transition: 'opacity 400ms ease-in', -}; +var formFieldCounter = 0, + formFieldNestedLabelTypes = ['radio', 'checkbox']; -var formFieldCounter = 0; +function formFieldId() { + return "form-field-" + (++formFieldCounter); +} -var FormField = React.createClass({ +export let FormField = React.createClass({ _fieldRequiredText: 'This field is required', _type: null, _element: null, propTypes: { type: React.PropTypes.string.isRequired, - row: React.PropTypes.bool, - hidden: React.PropTypes.bool, + hasError: React.PropTypes.bool }, getInitialState: function() { return { - errorState: 'hidden', - adviceText: null, + isError: null, + errorMessage: null, } }, componentWillMount: function() { - if (['text', 'radio', 'checkbox', 'file'].includes(this.props.type)) { + if (['text', 'number', 'radio', 'checkbox', 'file'].includes(this.props.type)) { this._element = 'input'; this._type = this.props.type; } else if (this.props.type == 'text-number') { @@ -38,22 +37,11 @@ var FormField = React.createClass({ }, showError: function(text) { this.setState({ - errorState: 'shown', - adviceText: text, + isError: true, + errorMessage: text, }); - - // setTimeout(() => { - // this.setState({ - // errorState: 'fading', - // }); - // setTimeout(() => { - // this.setState({ - // errorState: 'hidden', - // }); - // }, 450); - // }, 5000); }, - warnRequired: function() { + showRequiredError: function() { this.showError(this._fieldRequiredText); }, focus: function() { @@ -74,33 +62,27 @@ var FormField = React.createClass({ render: function() { // Pass all unhandled props to the field element const otherProps = Object.assign({}, this.props), - hasError = this.state.errorState != 'hidden'; + isError = this.state.isError !== null ? this.state.isError : this.props.hasError, + elementId = this.props.id ? this.props.id : formFieldId(), + renderElementInsideLabel = this.props.label && formFieldNestedLabelTypes.includes(this.props.type); + delete otherProps.type; - delete otherProps.hidden; delete otherProps.label; - delete otherProps.row; - delete otherProps.helper; + delete otherProps.hasError; - ++formFieldCounter; - const elementId = "form-field-" + formFieldCounter + const element = + {this.props.children} + ; - if (this.props.hidden) { - return null; - } - - const field =
- { this.props.label ? -
- -
: '' - } - - {this.props.children} - - { !hasError && this.props.helper ?
{this.props.helper}
: '' } - { hasError ?
{this.state.adviceText}
: '' } + return
+ { renderElementInsideLabel ? + : element } + { isError ?
{this.state.errorMessage}
: '' }
return ( this.props.row ? @@ -108,6 +90,57 @@ var FormField = React.createClass({ field ); } -}); +}) -export default FormField; +export let FormRow = React.createClass({ + propTypes: { + label: React.PropTypes.string, + // helper: React.PropTypes.html, + }, + getValue: function() { + if (this.props.type == 'checkbox') { + return this.refs.field.checked; + } else if (this.props.type == 'file') { + return this.refs.field.files[0].path; + } else { + return this.refs.field.value; + } + }, + getInitialState: function() { + return { + isError: false, + errorMessage: null, + } + }, + showError: function(text) { + this.setState({ + isError: true, + errorMessage: text, + }); + }, + getValue: function() { + return this.refs.field.getValue(); + }, + render: function() { + const fieldProps = Object.assign({}, this.props), + elementId = formFieldId(), + renderLabelInFormField = formFieldNestedLabelTypes.includes(this.props.type); + + if (!renderLabelInFormField) { + delete fieldProps.label; + } + delete fieldProps.helper; + + return
+ { this.props.label && !renderLabelInFormField ? +
+ +
: '' } + + { !this.state.isError && this.props.helper ?
{this.props.helper}
: '' } + { this.state.isError ?
{this.state.errorMessage}
: '' } +
+ } +}) diff --git a/ui/js/component/header.js b/ui/js/component/header.js index 6e186cc44..463042cff 100644 --- a/ui/js/component/header.js +++ b/ui/js/component/header.js @@ -1,6 +1,6 @@ import React from 'react'; import {Link} from './link.js'; -import NotificationBar from './notification-bar.js'; +import {Icon} from './common.js'; var Header = React.createClass({ getInitialState: function() { @@ -53,6 +53,7 @@ var Header = React.createClass({

{ this.state.title }

+
@@ -62,7 +63,6 @@ var Header = React.createClass({ : '' } - ); } diff --git a/ui/js/component/link.js b/ui/js/component/link.js index 8a4d76f76..8bcaddd48 100644 --- a/ui/js/component/link.js +++ b/ui/js/component/link.js @@ -1,5 +1,7 @@ import React from 'react'; import {Icon} from './common.js'; +import Modal from '../component/modal.js'; +import rewards from '../rewards.js'; export let Link = React.createClass({ propTypes: { @@ -52,4 +54,76 @@ export let Link = React.createClass({ ); } +}); + +export let RewardLink = React.createClass({ + propTypes: { + type: React.PropTypes.string.isRequired, + claimed: React.PropTypes.bool, + onRewardClaim: React.PropTypes.func + }, + refreshClaimable: function() { + switch(this.props.type) { + case 'new_user': + this.setState({ claimable: true }); + return; + + case 'first_publish': + lbry.claim_list_mine().then(function(list) { + this.setState({ + claimable: list.length > 0 + }) + }.bind(this)); + return; + } + }, + componentWillMount: function() { + this.refreshClaimable(); + }, + getInitialState: function() { + return { + claimable: true, + pending: false, + errorMessage: null + } + }, + claimReward: function() { + this.setState({ + pending: true + }) + rewards.claimReward(this.props.type).then(function(reward) { + console.log(reward); + this.setState({ + pending: false, + errorMessage: null + }) + if (this.props.onRewardClaim) { + this.props.onRewardClaim(); + } + }.bind(this)).catch(function(error) { + this.setState({ + errorMessage: error.message, + pending: false + }) + }.bind(this)) + }, + clearError: function() { + this.setState({ + errorMessage: null + }) + }, + render: function() { + return ( +
+ {this.props.claimed + ? Reward claimed. + : } + {this.state.errorMessage ? + + {this.state.errorMessage} + + : ''} +
+ ); + } }); \ No newline at end of file diff --git a/ui/js/component/notification-bar.js b/ui/js/component/notification-bar.js deleted file mode 100644 index f6662b552..000000000 --- a/ui/js/component/notification-bar.js +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import lbry from '../lbry.js'; -import Notice from '../component/notice.js'; - -export const NotificationBar = React.createClass({ - _displayTime: 8, // in seconds - - _hideTimeout: null, - - getInitialState: function() { - return { - message: null, - isError: null, - } - }, - handleNoticeReceived: function(event) { - if (this._hideTimeout) { - clearTimeout(this._hideTimeout); - } - - const {detail: {message, isError}} = event; - this.setState({ - message: message, - isError: isError, - }); - - this._hideTimeout = setTimeout(() => { - this.setState({ - message: null, - isError: null, - }); - }, this._displayTime * 1000); - }, - componentWillMount: function() { - document.addEventListener('globalNotice', this.handleNoticeReceived); - }, - componentWillUnmount: function() { - document.removeEventListener('globalNotice', this.handleNoticeReceived); - }, - render: function() { - if (!this.state.message) { - return null; - } - - return ( - - {this.state.message} - - ); - }, -}); - -export default NotificationBar; \ No newline at end of file diff --git a/ui/js/component/snack-bar.js b/ui/js/component/snack-bar.js new file mode 100644 index 000000000..e1ddb01b0 --- /dev/null +++ b/ui/js/component/snack-bar.js @@ -0,0 +1,58 @@ +import React from 'react'; +import lbry from '../lbry.js'; + +export const SnackBar = React.createClass({ + + _displayTime: 5, // in seconds + + _hideTimeout: null, + + getInitialState: function() { + return { + snacks: [] + } + }, + handleSnackReceived: function(event) { + // console.log(event); + // if (this._hideTimeout) { + // clearTimeout(this._hideTimeout); + // } + + let snacks = this.state.snacks; + snacks.push(event.detail); + this.setState({ snacks: snacks}); + }, + componentWillMount: function() { + document.addEventListener('globalNotice', this.handleSnackReceived); + }, + componentWillUnmount: function() { + document.removeEventListener('globalNotice', this.handleSnackReceived); + }, + render: function() { + if (!this.state.snacks.length) { + this._hideTimeout = null; //should be unmounting anyway, but be safe? + return null; + } + + let snack = this.state.snacks[0]; + + if (this._hideTimeout === null) { + this._hideTimeout = setTimeout(function() { + this._hideTimeout = null; + let snacks = this.state.snacks; + snacks.shift(); + this.setState({ snacks: snacks }); + }.bind(this), this._displayTime * 1000); + } + + return ( +
+ {snack.message} + {snack.linkText && snack.linkTarget ? + {snack.linkText} : ''} +
+ ); + }, +}); + +export default SnackBar; \ No newline at end of file diff --git a/ui/js/component/welcome.js b/ui/js/component/welcome.js deleted file mode 100644 index 36036abe1..000000000 --- a/ui/js/component/welcome.js +++ /dev/null @@ -1,156 +0,0 @@ -import React from 'react'; -import lbryio from '../lbryio.js'; - -import ModalPage from './modal-page.js'; -import {Link} from '../component/link.js'; -import FormField from '../component/form.js'; -import Notice from '../component/notice.js' - - -const SubmitEmailStage = React.createClass({ - getInitialState: function() { - return { - rewardType: null, - email: '', - submitting: false, - errorMessage: null, - }; - }, - handleEmailChanged: function(event) { - this.setState({ - email: event.target.value, - }); - }, - handleSubmit: function(event) { - event.preventDefault(); - - this.setState({ - submitting: true, - }); - lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => { - this.props.onDone(); - }, (error) => { - this.setState({ - submitting: false, - errorMessage: error.message, - }); - }); - }, - render: function() { - return ( -
-

Welcome to LBRY

- {this.state.errorMessage - ? - {this.state.errorMessage} - - : null} -

Copy here explaining what we do with your email, and the reward.

-
-
Email
-
-
-
- ); - } -}); - -const ConfirmEmailStage = React.createClass({ - getInitialState: function() { - return { - rewardType: null, - code: '', - submitting: false, - errorMessage: null, - }; - }, - handleCodeChanged: function(event) { - this.setState({ - code: event.target.value, - }); - }, - handleSubmit: function(event) { - event.preventDefault(); - this.setState({ - submitting: true, - }); - - lbryio.call('user_email', 'confirm', {verification_token: this.state.code}, 'post').then(() => { - rewards.claimReward('confirm_email').then(() => { - console.log('succeeded'); - this.props.onDone(); - }, (err) => { - console.log('failed'); - this.props.onDone(); - }); - }, (error) => { - this.setState({ - submitting: false, - errorMessage: error.message, - }); - }); - }, - render: function() { - return ( -
-

Confirm Your Email Address

- {this.state.errorMessage - ? - {this.state.errorMessage} - - : null} -

Please enter your verification code to confirm your email address.

-
-
-
-
-
- ); - } -}); - -const FinalMessageStage = React.createClass({ - render: function() { - return ( -
-

Email verified

-

Text here about what happens next

-
-
- ); - } -}); - -export const Welcome = React.createClass({ - _stages: [ - SubmitEmailStage, - ConfirmEmailStage, - FinalMessageStage, - ], - propTypes: { - onDone: React.PropTypes.func.isRequired, - }, - getInitialState: function() { - return { - stageNum: 0, - }; - }, - handleStageDone: function() { - if (this.state.stageNum >= this._stages.length - 1) { - this.props.onDone(); - } else { - this.setState({ - stageNum: this.state.stageNum + 1, - }); - } - }, - render: function() { - const Content = this._stages[this.state.stageNum]; - return ( - - - - ); - } -}); - diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 4d227248e..8fbfdb04d 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -282,29 +282,6 @@ lbry.getCostInfo = function(lbryUri, callback, errorCallback) { }); } -lbry.getFeaturedDiscoverNames = function(callback) { - return new Promise(function(resolve, reject) { - var xhr = new XMLHttpRequest; - xhr.open('GET', 'https://api.lbry.io/discover/list', true); - xhr.onload = () => { - if (xhr.status === 200) { - var responseData = JSON.parse(xhr.responseText); - if (responseData.data) //new signature, once api.lbry.io is updated - { - resolve(responseData.data); - } - else - { - resolve(responseData); - } - } else { - reject(Error('Failed to fetch featured names.')); - } - }; - xhr.send(); - }); -} - lbry.getMyClaims = function(callback) { lbry.call('get_name_claims', {}, callback); } diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 582592b71..9c6d37f03 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -1,4 +1,4 @@ -import {getLocal, setLocal} from './utils.js'; +import {getLocal, getSession, setSession, setLocal} from './utils.js'; import lbry from './lbry.js'; const querystring = require('querystring'); @@ -20,18 +20,7 @@ const mocks = { value: 50, claimed: false, }; - }, - 'reward_type.list': () => { - return [ - { - name: 'link_github', - title: 'Link your GitHub account', - description: 'Link LBRY to your GitHub account', - value: 50, - claimed: false, - }, - ]; - }, + } }; lbryio.call = function(resource, action, params={}, method='get') { @@ -103,7 +92,7 @@ lbryio.setAccessToken = (token) => { lbryio._accessToken = token } -lbryio.authenticate = () => { +lbryio.authenticate = function() { if (lbryio._authenticationPromise === null) { lbryio._authenticationPromise = new Promise((resolve, reject) => { lbry.status().then(({installation_id}) => { @@ -117,7 +106,12 @@ lbryio.authenticate = () => { resolve(data) }).catch(function(err) { lbryio.setAccessToken(null); - reject(err); + if (!getSession('reloadedOnFailedAuth')) { + setSession('reloadedOnFailedAuth', true) + window.location.reload(); + } else { + reject(err); + } }) } diff --git a/ui/js/main.js b/ui/js/main.js index 12dee5d92..544ebb0a2 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -5,8 +5,8 @@ import lbryio from './lbryio.js'; import lighthouse from './lighthouse.js'; import App from './app.js'; import SplashScreen from './component/splash.js'; +import SnackBar from './component/snack-bar.js'; import {AuthOverlay} from './component/auth.js'; -import {Welcome} from './component/welcome.js'; const {remote} = require('electron'); const contextMenu = remote.require('./menu/context-menu'); @@ -28,12 +28,12 @@ let init = function() { lbryio.authenticate() //start auth process as soon as soon as we can get an install ID }) - async function onDaemonReady() { + function onDaemonReady() { window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again - ReactDOM.render(
, canvas) + ReactDOM.render(
, canvas) } - if (window.sessionStorage.getItem('loaded') == 'y' && false) { + if (window.sessionStorage.getItem('loaded') == 'y') { onDaemonReady(); } else { ReactDOM.render(, canvas); diff --git a/ui/js/page/claim_code.js b/ui/js/page/claim_code.js deleted file mode 100644 index 7a9976824..000000000 --- a/ui/js/page/claim_code.js +++ /dev/null @@ -1,158 +0,0 @@ -import React from 'react'; -import lbry from '../lbry.js'; -import Modal from '../component/modal.js'; -import {Link} from '../component/link.js'; - -var claimCodeContentStyle = { - display: 'inline-block', - textAlign: 'left', - width: '600px', -}, claimCodeLabelStyle = { - display: 'inline-block', - cursor: 'default', - width: '130px', - textAlign: 'right', - marginRight: '6px', -}; - -var ClaimCodePage = React.createClass({ - getInitialState: function() { - return { - submitting: false, - modal: null, - referralCredits: null, - activationCredits: null, - failureReason: null, - } - }, - handleSubmit: function(event) { - if (typeof event !== 'undefined') { - event.preventDefault(); - } - - if (!this.refs.code.value) { - this.setState({ - modal: 'missingCode', - }); - return; - } else if (!this.refs.email.value) { - this.setState({ - modal: 'missingEmail', - }); - return; - } - - this.setState({ - submitting: true, - }); - - lbry.getUnusedAddress((address) => { - var code = this.refs.code.value; - var email = this.refs.email.value; - - var xhr = new XMLHttpRequest; - xhr.addEventListener('load', () => { - var response = JSON.parse(xhr.responseText); - - if (response.success) { - this.setState({ - modal: 'codeRedeemed', - referralCredits: response.referralCredits, - activationCredits: response.activationCredits, - }); - } else { - this.setState({ - submitting: false, - modal: 'codeRedeemFailed', - failureReason: response.reason, - }); - } - }); - - xhr.addEventListener('error', () => { - this.setState({ - submitting: false, - modal: 'couldNotConnect', - }); - }); - - xhr.open('POST', 'https://invites.lbry.io', true); - xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - xhr.send('code=' + encodeURIComponent(code) + '&address=' + encodeURIComponent(address) + - '&email=' + encodeURIComponent(email)); - }); - }, - handleSkip: function() { - this.setState({ - modal: 'skipped', - }); - }, - handleFinished: function() { - localStorage.setItem('claimCodeDone', true); - window.location = '?home'; - }, - closeModal: function() { - this.setState({ - modal: null, - }); - }, - render: function() { - return ( -
-
-
-

Claim your beta invitation code

-
-

Thanks for beta testing LBRY! Enter your invitation code and email address below to receive your initial - LBRY credits.

-

You will be added to our mailing list (if you're not already on it) and will be eligible for future rewards for beta testers.

-
-
-
-
-
-
- - - -
-
-
- - Please enter an invitation code or choose "Skip." - - - Please enter an email address or choose "Skip." - - - {this.state.failureReason} - - - Your invite code has been redeemed. { ' ' } - {this.state.referralCredits > 0 - ? `You have also earned ${referralCredits} credits from referrals. A total of ${activationCredits + referralCredits} - will be added to your balance shortly.` - : (this.state.activationCredits > 0 - ? `${this.state.activationCredits} credits will be added to your balance shortly.` - : 'The credits will be added to your balance shortly.')} - - - Welcome to LBRY! You can visit the Wallet page to redeem an invite code at any time. - - -

LBRY couldn't connect to our servers to confirm your invitation code. Please check your internet connection.

- If you continue to have problems, you can still browse LBRY and visit the Settings page to redeem your code later. -
-
- ); - } -}); - -export default ClaimCodePage; diff --git a/ui/js/page/developer.js b/ui/js/page/developer.js index 93eb1cc11..377204852 100644 --- a/ui/js/page/developer.js +++ b/ui/js/page/developer.js @@ -1,6 +1,6 @@ import lbry from '../lbry.js'; import React from 'react'; -import FormField from '../component/form.js'; +import {FormField} from '../component/form.js'; import {Link} from '../component/link.js'; const fs = require('fs'); diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js index 993481d07..7751db2db 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover.js @@ -1,5 +1,6 @@ import React from 'react'; import lbry from '../lbry.js'; +import lbryio from '../lbryio.js'; import lighthouse from '../lighthouse.js'; import {FileTile} from '../component/file-tile.js'; import {Link} from '../component/link.js'; @@ -58,45 +59,44 @@ var SearchResults = React.createClass({ } }); -var featuredContentLegendStyle = { - fontSize: '12px', - color: '#aaa', - verticalAlign: '15%', -}; +const communityCategoryToolTipText = ('Community Content is a public space where anyone can share content with the ' + +'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' + +'"five" to put your content here!'); + +var FeaturedCategory = React.createClass({ + render: function() { + return (
+ { this.props.category ? +

{this.props.category} + { this.props.category == "community" ? + + : '' }

+ : '' } + { this.props.names.map((name) => { return }) } +
) + } +}) var FeaturedContent = React.createClass({ getInitialState: function() { return { - featuredNames: [], + featuredNames: {}, }; }, componentWillMount: function() { - lbry.getFeaturedDiscoverNames().then((featuredNames) => { + lbryio.call('discover', 'list', { version: "early-access" } ).then((featuredNames) => { this.setState({ featuredNames: featuredNames }); }); }, render: function() { - const toolTipText = ('Community Content is a public space where anyone can share content with the ' + - 'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' + - '"five" to put your content here!'); - + console.log(this.state.featuredNames); return ( -
-
-

Featured Content

- { this.state.featuredNames.map(name => ) } -
-
-

- Community Content - -

- - - - - -
+
+ { + Object.keys(this.state.featuredNames).map(function(category) { + return + }.bind(this)) + }
); } @@ -173,12 +173,12 @@ var DiscoverPage = React.createClass({ }, render: function() { - //{ !this.props.query && !this.state.searching ? : null } return (
{ this.state.searching ? : null } { !this.state.searching && this.props.query && this.state.results.length ? : null } { !this.state.searching && this.props.query && !this.state.results.length ? : null } + { !this.props.query && !this.state.searching ? : null }
); } diff --git a/ui/js/page/email.js b/ui/js/page/email.js index b7f31cd49..76b031737 100644 --- a/ui/js/page/email.js +++ b/ui/js/page/email.js @@ -1,7 +1,7 @@ import React from 'react'; import lbryio from '../lbryio.js'; import {getLocal, setLocal} from '../utils.js'; -import FormField from '../component/form.js' +import {FormField} from '../component/form.js' import {Link} from '../component/link.js' import rewards from '../rewards.js'; @@ -12,7 +12,7 @@ const EmailPage = React.createClass({ } if (!this.state.email) { - this._emailField.warnRequired(); + this._emailField.showRequiredError(); } }, componentWillMount: function() { diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index ba91835e7..518bb85d6 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -2,7 +2,7 @@ import React from 'react'; import lbry from '../lbry.js'; import uri from '../uri.js'; import {Link} from '../component/link.js'; -import FormField from '../component/form.js'; +import {FormField} from '../component/form.js'; import {FileTileStream} from '../component/file-tile.js'; import {BusyMessage, Thumbnail} from '../component/common.js'; diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 6e0799263..4f28dc567 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -1,7 +1,7 @@ import React from 'react'; import lbry from '../lbry.js'; import uri from '../uri.js'; -import FormField from '../component/form.js'; +import {FormField, FormRow} from '../component/form.js'; import {Link} from '../component/link.js'; import Modal from '../component/modal.js'; @@ -36,7 +36,7 @@ var PublishPage = React.createClass({ for (let fieldName of checkFields) { var field = this.refs[fieldName]; if (field.getValue() === '') { - field.warnRequired(); + field.showRequiredError(); if (!missingFieldFound) { field.focus(); missingFieldFound = true; @@ -84,7 +84,7 @@ var PublishPage = React.createClass({ if (this.refs.file.getValue() !== '') { publishArgs.file_path = this.refs.file.getValue(); } - + lbry.publish(publishArgs, (message) => { this.handlePublishStarted(); }, null, (error) => { @@ -344,9 +344,12 @@ var PublishPage = React.createClass({
-

LBRY Name

-
- +
+

LBRY Name

+
+
+ What LBRY name would you like to claim for this file? .
)} /> { (!this.state.name ? null @@ -356,7 +359,6 @@ var PublishPage = React.createClass({ ? You already have a claim on the name {this.state.name}. You can use this page to update your claim. : The name {this.state.name} is currently claimed for {this.state.topClaimValue} {this.state.topClaimValue == 1 ? 'credit' : 'credits'}.))) } -
What LBRY name would you like to claim for this file?
@@ -381,9 +383,11 @@ var PublishPage = React.createClass({
-

Choose File

- - { this.state.myClaimExists ?
If you don't choose a file, the file from your existing claim will be used.
: null } +

Choose File

+
+ + { this.state.myClaimExists ?
If you don't choose a file, the file from your existing claim will be used.
: null } +
diff --git a/ui/js/page/referral.js b/ui/js/page/referral.js deleted file mode 100644 index 1f98e49ff..000000000 --- a/ui/js/page/referral.js +++ /dev/null @@ -1,130 +0,0 @@ -import React from 'react'; -import lbry from '../lbry.js'; -import {Link} from '../component/link.js'; -import Modal from '../component/modal.js'; - -var referralCodeContentStyle = { - display: 'inline-block', - textAlign: 'left', - width: '600px', -}, referralCodeLabelStyle = { - display: 'inline-block', - cursor: 'default', - width: '130px', - textAlign: 'right', - marginRight: '6px', -}; - -var ReferralPage = React.createClass({ - getInitialState: function() { - return { - submitting: false, - modal: null, - referralCredits: null, - failureReason: null, - } - }, - handleSubmit: function(event) { - if (typeof event !== 'undefined') { - event.preventDefault(); - } - - if (!this.refs.code.value) { - this.setState({ - modal: 'missingCode', - }); - } else if (!this.refs.email.value) { - this.setState({ - modal: 'missingEmail', - }); - } - - this.setState({ - submitting: true, - }); - - lbry.getUnusedAddress((address) => { - var code = this.refs.code.value; - var email = this.refs.email.value; - - var xhr = new XMLHttpRequest; - xhr.addEventListener('load', () => { - var response = JSON.parse(xhr.responseText); - - if (response.success) { - this.setState({ - modal: 'referralInfo', - referralCredits: response.referralCredits, - }); - } else { - this.setState({ - submitting: false, - modal: 'lookupFailed', - failureReason: response.reason, - }); - } - }); - - xhr.addEventListener('error', () => { - this.setState({ - submitting: false, - modal: 'couldNotConnect', - }); - }); - - xhr.open('POST', 'https://invites.lbry.io/check', true); - xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - xhr.send('code=' + encodeURIComponent(code) + '&address=' + encodeURIComponent(address) + - '&email=' + encodeURIComponent(email)); - }); - }, - closeModal: function() { - this.setState({ - modal: null, - }); - }, - handleFinished: function() { - localStorage.setItem('claimCodeDone', true); - window.location = '?home'; - }, - render: function() { - return ( -
- -
-

Check your referral credits

-
-

Have you referred others to LBRY? Enter your referral code and email address below to check how many credits you've earned!

-

As a reminder, your referral code is the same as your LBRY invitation code.

-
-
-
-
-
-
- - -
-
- - - {this.state.referralCredits > 0 - ? `You have earned ${response.referralCredits} credits from referrals. We will credit your account shortly. Thanks!` - : 'You have not earned any new referral credits since the last time you checked. Please check back in a week or two.'} - - - {this.state.failureReason} - - - LBRY couldn't connect to our servers to confirm your referral code. Please check your internet connection. - -
- ); - } -}); - -export default ReferralPage; diff --git a/ui/js/page/reward.js b/ui/js/page/reward.js index 773e21893..2fb5b3e64 100644 --- a/ui/js/page/reward.js +++ b/ui/js/page/reward.js @@ -3,124 +3,124 @@ import lbryio from '../lbryio.js'; import {Link} from '../component/link.js'; import Notice from '../component/notice.js'; import {CreditAmount} from '../component/common.js'; - -const {shell} = require('electron'); -const querystring = require('querystring'); - -const GITHUB_CLIENT_ID = '6baf581d32bad60519'; - -const LinkGithubReward = React.createClass({ - propTypes: { - onClaimed: React.PropTypes.func, - }, - _launchLinkPage: function() { - /* const githubAuthParams = { - client_id: GITHUB_CLIENT_ID, - redirect_uri: 'https://lbry.io/', - scope: 'user:email,public_repo', - allow_signup: false, - } - shell.openExternal('https://github.com/login/oauth/authorize?' + querystring.stringify(githubAuthParams)); */ - shell.openExternal('https://lbry.io'); - }, - handleConfirmClicked: function() { - this.setState({ - confirming: true, - }); - - lbry.get_new_address().then((address) => { - lbryio.call('reward', 'new', { - reward_type: 'new_developer', - access_token: '**access token here**', - wallet_address: address, - }, 'post').then((response) => { - console.log('response:', response); - - this.props.onClaimed(); // This will trigger another API call to show that we succeeded - - this.setState({ - confirming: false, - error: null, - }); - }, (error) => { - console.log('failed with error:', error); - this.setState({ - confirming: false, - error: error, - }); - }); - }); - }, - getInitialState: function() { - return { - confirming: false, - error: null, - }; - }, - render: function() { - return ( -
-

-
-

This will open a browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.

-

Once you're finished, you may confirm you've linked the account to receive your reward.

-
- {this.state.error - ? - {this.state.error.message} - - : null} - - -
- ); - } -}); - -const RewardPage = React.createClass({ - propTypes: { - name: React.PropTypes.string.isRequired, - }, - _getRewardType: function() { - lbryio.call('reward_type', 'get', this.props.name).then((rewardType) => { - this.setState({ - rewardType: rewardType, - }); - }); - }, - getInitialState: function() { - return { - rewardType: null, - }; - }, - componentWillMount: function() { - this._getRewardType(); - }, - render: function() { - if (!this.state.rewardType) { - return null; - } - - let Reward; - if (this.props.name == 'link_github') { - Reward = LinkGithubReward; - } - - const {title, description, value} = this.state.rewardType; - return ( -
-
-

{title}

- -

{this.state.rewardType.claimed - ? This reward has been claimed. - : description}

- -
-
- ); - } -}); - -export default RewardPage; +// +// const {shell} = require('electron'); +// const querystring = require('querystring'); +// +// const GITHUB_CLIENT_ID = '6baf581d32bad60519'; +// +// const LinkGithubReward = React.createClass({ +// propTypes: { +// onClaimed: React.PropTypes.func, +// }, +// _launchLinkPage: function() { +// /* const githubAuthParams = { +// client_id: GITHUB_CLIENT_ID, +// redirect_uri: 'https://lbry.io/', +// scope: 'user:email,public_repo', +// allow_signup: false, +// } +// shell.openExternal('https://github.com/login/oauth/authorize?' + querystring.stringify(githubAuthParams)); */ +// shell.openExternal('https://lbry.io'); +// }, +// handleConfirmClicked: function() { +// this.setState({ +// confirming: true, +// }); +// +// lbry.get_new_address().then((address) => { +// lbryio.call('reward', 'new', { +// reward_type: 'new_developer', +// access_token: '**access token here**', +// wallet_address: address, +// }, 'post').then((response) => { +// console.log('response:', response); +// +// this.props.onClaimed(); // This will trigger another API call to show that we succeeded +// +// this.setState({ +// confirming: false, +// error: null, +// }); +// }, (error) => { +// console.log('failed with error:', error); +// this.setState({ +// confirming: false, +// error: error, +// }); +// }); +// }); +// }, +// getInitialState: function() { +// return { +// confirming: false, +// error: null, +// }; +// }, +// render: function() { +// return ( +//
+//

+//
+//

This will open a browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.

+//

Once you're finished, you may confirm you've linked the account to receive your reward.

+//
+// {this.state.error +// ? +// {this.state.error.message} +// +// : null} +// +// +//
+// ); +// } +// }); +// +// const RewardPage = React.createClass({ +// propTypes: { +// name: React.PropTypes.string.isRequired, +// }, +// _getRewardType: function() { +// lbryio.call('reward_type', 'get', this.props.name).then((rewardType) => { +// this.setState({ +// rewardType: rewardType, +// }); +// }); +// }, +// getInitialState: function() { +// return { +// rewardType: null, +// }; +// }, +// componentWillMount: function() { +// this._getRewardType(); +// }, +// render: function() { +// if (!this.state.rewardType) { +// return null; +// } +// +// let Reward; +// if (this.props.name == 'link_github') { +// Reward = LinkGithubReward; +// } +// +// const {title, description, value} = this.state.rewardType; +// return ( +//
+//
+//

{title}

+// +//

{this.state.rewardType.claimed +// ? This reward has been claimed. +// : description}

+// +//
+//
+// ); +// } +// }); +// +// export default RewardPage; diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js index 52f705035..62c5d0497 100644 --- a/ui/js/page/rewards.js +++ b/ui/js/page/rewards.js @@ -1,28 +1,34 @@ import React from 'react'; import lbry from '../lbry.js'; import lbryio from '../lbryio.js'; -import {CreditAmount} from '../component/common.js'; +import {CreditAmount, Icon} from '../component/common.js'; +import rewards from '../rewards.js'; import Modal from '../component/modal.js'; -import {Link} from '../component/link.js'; +import {RewardLink} from '../component/link.js'; const RewardTile = React.createClass({ propTypes: { - name: React.PropTypes.string.isRequired, + type: React.PropTypes.string.isRequired, title: React.PropTypes.string.isRequired, description: React.PropTypes.string.isRequired, claimed: React.PropTypes.bool.isRequired, value: React.PropTypes.number.isRequired, + onRewardClaim: React.PropTypes.func }, render: function() { return (
-
-

- -
{this.props.description}
- {this.props.claimed - ? This reward has been claimed. - : } +
+
+ +

{this.props.title}

+
+
+ {this.props.claimed + ? Reward claimed. + : } +
+
{this.props.description}
); @@ -31,29 +37,29 @@ const RewardTile = React.createClass({ var RewardsPage = React.createClass({ componentWillMount: function() { - lbryio.call('reward_type', 'list', {}).then((rewardTypes) => { - this.setState({ - rewardTypes: rewardTypes, - }); - }); + this.loadRewards() }, getInitialState: function() { return { - rewardTypes: null, + userRewards: null, }; }, + loadRewards: function() { + lbryio.call('reward', 'list', {}).then((userRewards) => { + this.setState({ + userRewards: userRewards, + }); + }); + }, render: function() { return (
-
-

Rewards

- {!this.state.rewardTypes - ? null - : this.state.rewardTypes.map(({name, title, description, claimed, value}) => { - return ; - })} -
+ {!this.state.userRewards + ? null + : this.state.userRewards.map(({RewardType, RewardTitle, RewardDescription, TransactionID, RewardAmount}) => { + return ; + })}
); diff --git a/ui/js/page/settings.js b/ui/js/page/settings.js index 508a8a84d..c278741b4 100644 --- a/ui/js/page/settings.js +++ b/ui/js/page/settings.js @@ -1,21 +1,7 @@ import React from 'react'; +import {FormField, FormRow} from '../component/form.js'; import lbry from '../lbry.js'; -var settingsRadioOptionStyles = { - display: 'block', - marginLeft: '13px' -}, settingsCheckBoxOptionStyles = { - display: 'block', - marginLeft: '13px' -}, settingsNumberFieldStyles = { - width: '40px' -}, downloadDirectoryLabelStyles = { - fontSize: '.9em', - marginLeft: '13px' -}, downloadDirectoryFieldStyles= { - width: '300px' -}; - var SettingsPage = React.createClass({ onRunOnStartChange: function (event) { lbry.setDaemonSetting('run_on_startup', event.target.checked); @@ -81,29 +67,54 @@ var SettingsPage = React.createClass({ return (
-

Run on Startup

- -
-
-

Download Directory

-
Where would you like the files you download from LBRY to be saved?
- -
-
-

Bandwidth Limits

-
-

Max Upload

- - +
+

Run on Startup

-
+
+ +
+
+
+
+

Download Directory

+
+
+ +
+
+
+
+

Bandwidth Limits

+
+
+

Max Upload

+ + + { this.state.isMaxUpload ? + : '' + } +
+

Max Download

-

Content

-
- -
- NSFW content may include nudity, intense sexuality, profanity, or other adult content. - By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction. -
+
+

Content

+
+
+ +
+
+
-

Search

-
-
- Would you like search results to include items that are not currently available for download? +
+

Share Diagnostic Data

- +
+
-
-

Share Diagnostic Data

- -
); } }); +/* + +
+

Search

+
+
+ Would you like search results to include items that are not currently available for download? +
+ +
+
+
+

Share Diagnostic Data

+ +
+ */ export default SettingsPage; diff --git a/ui/js/page/wallet.js b/ui/js/page/wallet.js index 2ace64c27..fd68345cb 100644 --- a/ui/js/page/wallet.js +++ b/ui/js/page/wallet.js @@ -2,12 +2,9 @@ import React from 'react'; import lbry from '../lbry.js'; import {Link} from '../component/link.js'; import Modal from '../component/modal.js'; +import {FormField, FormRow} from '../component/form.js'; import {Address, BusyMessage, CreditAmount} from '../component/common.js'; - -var addressRefreshButtonStyle = { - fontSize: '11pt', -}; var AddressSection = React.createClass({ _refreshAddress: function(event) { if (typeof event !== 'undefined') { @@ -27,12 +24,12 @@ var AddressSection = React.createClass({ event.preventDefault(); } - lbry.getNewAddress((address) => { + lbry.wallet_new_address().then((address) => { window.localStorage.setItem('wallet_address', address); this.setState({ address: address, }); - }); + }.bind(this)); }, getInitialState: function() { @@ -60,12 +57,20 @@ var AddressSection = React.createClass({ render: function() { return (
-

Wallet Address

-
- -
-

Other LBRY users may send credits to you by entering this address on the "Send" page.

- You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources. +
+

Wallet Address

+
+
+
+
+
+ +
+
+
+

Other LBRY users may send credits to you by entering this address on the "Send" page.

+

You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.

+
); @@ -143,27 +148,26 @@ var SendToAddressSection = React.createClass({ return (
-

Send Credits

-
- - +
+

Send Credits

-
- - +
+
-
+
+ +
+
0.0) || this.state.address == ""} />
- { - this.state.results ? -
-

Results

- {this.state.results} -
- : '' - } + { + this.state.results ? +
+

Results

+ {this.state.results} +
: '' + } @@ -231,25 +235,29 @@ var TransactionList = React.createClass({ } return (
-

Transaction History

- { this.state.transactionItems === null ? : '' } - { this.state.transactionItems && rows.length === 0 ?
You have no transactions.
: '' } - { this.state.transactionItems && rows.length > 0 ? - - - - - - - - - - - {rows} - -
AmountDateTimeTransaction
+
+

Transaction History

+
+
+ { this.state.transactionItems === null ? : '' } + { this.state.transactionItems && rows.length === 0 ?
You have no transactions.
: '' } + { this.state.transactionItems && rows.length > 0 ? + + + + + + + + + + + {rows} + +
AmountDateTimeTransaction
: '' - } + } +
); } @@ -290,9 +298,13 @@ var WalletPage = React.createClass({ return (
-

Balance

- { this.state.balance === null ? : ''} - { this.state.balance !== null ? : '' } +
+

Balance

+
+
+ { this.state.balance === null ? : ''} + { this.state.balance !== null ? : '' } +
{ this.props.viewingPage === 'wallet' ? : '' } { this.props.viewingPage === 'send' ? : '' } diff --git a/ui/js/rewards.js b/ui/js/rewards.js index 4310b18b8..29f43058c 100644 --- a/ui/js/rewards.js +++ b/ui/js/rewards.js @@ -1,11 +1,14 @@ import lbry from './lbry.js'; import lbryio from './lbryio.js'; -const MESSAGES = { - new_developer: "Your reward has been confirmed for registering as a new developer.", - confirm_email: "Your reward has been confirmed for verifying your email address.", - first_publish: "Your reward has been confirmed for making your first publication.", -}; +function rewardMessage(type, amount) { + return { + new_developer: "Your reward has been confirmed for registering as a new developer.", + new_user: `You received ${amount} LBC new user reward.`, + confirm_email: "Your reward has been confirmed for verifying your email address.", + first_publish: "Your reward has been confirmed for making your first publication.", + }[type]; +} const rewards = {}; @@ -13,22 +16,25 @@ rewards.claimReward = function(type) { return new Promise((resolve, reject) => { console.log('top of promise body') lbry.get_new_address().then((address) => { - console.log('top of get_new_address') const params = { reward_type: type, wallet_address: address, }; lbryio.call('reward', 'new', params, 'post').then(({RewardAmount}) => { - const result = { - type: type, - amount: RewardAmount, - message: MESSAGES[type], - }; + const + message = rewardMessage(type, RewardAmount), + result = { + type: type, + amount: RewardAmount, + message: message + }; // Display global notice document.dispatchEvent(new CustomEvent('globalNotice', { detail: { - message: MESSAGES[type], + message: message, + linkText: "Show All", + linkTarget: "?rewards", isError: false, }, })); @@ -36,16 +42,7 @@ rewards.claimReward = function(type) { // Add more events here to display other places resolve(result); - }, (error) => { - document.dispatchEvent(new CustomEvent('globalNotice', { - detail: { - message: `Failed to claim reward: ${error.message}`, - isError: true, - }, - })); - document.dispatchEvent(new CustomEvent('rewardFailed', error)); - reject(error); - }); + }, reject); }); }); } diff --git a/ui/js/utils.js b/ui/js/utils.js index 290a0f54e..e9472a6a4 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -12,4 +12,20 @@ export function getLocal(key) { */ export function setLocal(key, value) { localStorage.setItem(key, JSON.stringify(value)); +} + +/** + * Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value + * is not set yet. + */ +export function getSession(key) { + const itemRaw = sessionStorage.getItem(key); + return itemRaw === null ? undefined : JSON.parse(itemRaw); +} + +/** + * Thin wrapper around localStorage.setItem(). Converts value to JSON. + */ +export function setSession(key, value) { + sessionStorage.setItem(key, JSON.stringify(value)); } \ No newline at end of file diff --git a/ui/scss/_canvas.scss b/ui/scss/_canvas.scss index a5082d0d9..300ecb75b 100644 --- a/ui/scss/_canvas.scss +++ b/ui/scss/_canvas.scss @@ -60,6 +60,11 @@ $drawer-width: 240px; text-align: center; } +#window +{ + position: relative; /*window has it's own z-index inside of it*/ + z-index: 1; +} #window.drawer-closed { #drawer { display: none } @@ -100,12 +105,28 @@ $drawer-width: 240px; .header-search { margin-left: 60px; + $padding-adjust: 36px; text-align: center; + .icon { + position: absolute; + top: $spacing-vertical * 1.5 / 2 + 2px; //hacked + margin-left: -$padding-adjust + 14px; //hacked + } input[type="search"] { + position: relative; + left: -$padding-adjust; background: rgba(255, 255, 255, 0.3); color: white; width: 400px; + height: $spacing-vertical * 1.5; + line-height: $spacing-vertical * 1.5; + padding-left: $padding-adjust + 3; + padding-right: 3px; + @include border-radius(2px); @include placeholder-color(#e8e8e8); + &:focus { + box-shadow: $focus-box-shadow; + } } } @@ -159,26 +180,6 @@ nav.sub-header { padding: $spacing-vertical; } - h2 - { - margin-bottom: $spacing-vertical; - } - h3, h4 - { - margin-bottom: $spacing-vertical / 2; - margin-top: $spacing-vertical; - &:first-child - { - margin-top: 0; - } - } - .meta - { - + h2, + h3, + h4 - { - margin-top: 0; - } - } } $header-icon-size: 1.5em; @@ -197,48 +198,6 @@ $header-icon-size: 1.5em; padding: 0 6px 0 18px; } -.card { - margin-left: auto; - margin-right: auto; - max-width: 800px; - padding: $spacing-vertical; - background: $color-bg; - box-shadow: $default-box-shadow; - border-radius: 2px; -} -.card-obscured -{ - position: relative; -} -.card-obscured .card-content { - -webkit-filter: blur($blur-intensity); - -moz-filter: blur($blur-intensity); - -o-filter: blur($blur-intensity); - -ms-filter: blur($blur-intensity); - filter: blur($blur-intensity); -} -.card-overlay { - position: absolute; - left: 0px; - right: 0px; - top: 0px; - bottom: 0px; - padding: 20px; - background-color: rgba(128, 128, 128, 0.8); - color: #fff; - display: flex; - align-items: center; - font-weight: 600; -} - -.card-series-submit -{ - margin-left: auto; - margin-right: auto; - max-width: 800px; - padding: $spacing-vertical / 2; -} - .full-screen { width: 100%; diff --git a/ui/scss/_form.scss b/ui/scss/_form.scss deleted file mode 100644 index c54343c4a..000000000 --- a/ui/scss/_form.scss +++ /dev/null @@ -1,66 +0,0 @@ -@import "global"; - -/* this probably shouldn't exist but it does so here we are - Jeremy */ - - -textarea, -select, -input[type="text"], -input[type="password"], -input[type="email"], -input[type="number"], -input[type="search"], -input[type="date"] { - @include placeholder { - color: lighten($color-text-dark, 60%); - } - transition: all $transition-standard; - cursor: pointer; - padding-left: 1px; - padding-right: 1px; - box-sizing: border-box; - -webkit-appearance: none; - &[readonly] { - background-color: #bbb; - } -} - -input[type="text"], -input[type="password"], -input[type="email"], -input[type="number"], -input[type="search"], -input[type="date"] { - border-bottom: 2px solid $color-form-border; - line-height: $spacing-vertical - 4; - height: $spacing-vertical * 1.5; - &.form-field__input--error { - border-color: $color-error; - } -} - -textarea:focus, -input[type="text"]:focus, -input[type="password"]:focus, -input[type="email"]:focus, -input[type="number"]:focus, -input[type="search"]:focus, -input[type="date"]:focus { - border-color: $color-primary; -} - -textarea { - border: 2px solid $color-form-border; -} - -.form-row -{ - + .form-row - { - margin-top: $spacing-vertical; - } - + .form-row-submit - { - margin-top: $spacing-vertical; - } -} \ No newline at end of file diff --git a/ui/scss/_global.scss b/ui/scss/_global.scss index 7d3249888..b203e7d18 100644 --- a/ui/scss/_global.scss +++ b/ui/scss/_global.scss @@ -6,8 +6,10 @@ $padding-button: 12px; $padding-text-link: 4px; $color-primary: #155B4A; +$color-primary-light: saturate(lighten($color-primary, 50%), 20%); $color-light-alt: hsl(hue($color-primary), 15, 85); $color-text-dark: #000; +$color-black-transparent: rgba(32,32,32,0.9); $color-help: rgba(0,0,0,.6); $color-notice: #8a6d3b; $color-error: #a94442; @@ -30,7 +32,7 @@ $height-header: $spacing-vertical * 2.5; $height-button: $spacing-vertical * 1.5; $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); +$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); $transition-standard: .225s ease; diff --git a/ui/scss/_gui.scss b/ui/scss/_gui.scss index 691ccb328..6c97b5fbc 100644 --- a/ui/scss/_gui.scss +++ b/ui/scss/_gui.scss @@ -38,6 +38,7 @@ text-align: center; } +/* section { margin-bottom: $spacing-vertical; @@ -46,10 +47,10 @@ section margin-bottom: 0; } &:only-child { - /* If it's an only child, assume it's part of a React layout that will handle the last child condition on its own */ margin-bottom: $spacing-vertical; } } +*/ main h1 { font-size: 2.0em; @@ -178,7 +179,8 @@ p background: rgb(255, 255, 255); overflow: auto; border-radius: 4px; - padding: 36px; + padding: $spacing-vertical; + box-shadow: $default-box-shadow; max-width: 250px; } diff --git a/ui/scss/_reset.scss b/ui/scss/_reset.scss index 8e0db2623..e951875a8 100644 --- a/ui/scss/_reset.scss +++ b/ui/scss/_reset.scss @@ -7,6 +7,10 @@ body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fiel { outline: 0; } +input::-webkit-search-cancel-button { + /* Remove default */ + -webkit-appearance: none; +} table { border-collapse: collapse; diff --git a/ui/scss/all.scss b/ui/scss/all.scss index 3c28c011c..18a9ec720 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -1,12 +1,12 @@ @import "_reset"; @import "_grid"; @import "_icons"; -@import "_form"; @import "_mediaelement"; @import "_canvas"; @import "_gui"; @import "component/_table"; @import "component/_button.scss"; +@import "component/_card.scss"; @import "component/_file-actions.scss"; @import "component/_file-tile.scss"; @import "component/_form-field.scss"; @@ -16,7 +16,7 @@ @import "component/_channel-indicator.scss"; @import "component/_notice.scss"; @import "component/_modal-page.scss"; -@import "component/_notification-bar.scss"; +@import "component/_snack-bar.scss"; @import "page/_developer.scss"; @import "page/_watch.scss"; @import "page/_reward.scss"; diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss new file mode 100644 index 000000000..af42624b2 --- /dev/null +++ b/ui/scss/component/_card.scss @@ -0,0 +1,122 @@ +@import "../global"; + +$padding-card-horizontal: $spacing-vertical * 2/3; + +.card { + margin-left: auto; + margin-right: auto; + max-width: 800px; + background: $color-bg; + box-shadow: $default-box-shadow; + border-radius: 2px; + margin-bottom: $spacing-vertical * 2/3; + overflow: auto; +} +.card--obscured +{ + position: relative; +} +.card--obscured .card__inner { + -webkit-filter: blur($blur-intensity); + -moz-filter: blur($blur-intensity); + -o-filter: blur($blur-intensity); + -ms-filter: blur($blur-intensity); + filter: blur($blur-intensity); +} +.card__title-primary { + padding: 0 $padding-card-horizontal; + margin-top: $spacing-vertical; +} +.card__title-identity { + padding: 0 $padding-card-horizontal; + margin-top: $spacing-vertical * 1/3; + margin-bottom: $spacing-vertical * 1/3; +} +.card__actions { + padding: 0 $padding-card-horizontal; +} +.card__actions { + margin-top: $spacing-vertical * 2/3; +} +.card__actions--bottom { + margin-top: $spacing-vertical * 1/3; + margin-bottom: $spacing-vertical * 1/3; +} +.card__actions--form-submit { + margin-top: $spacing-vertical; + margin-bottom: $spacing-vertical * 2/3; +} +.card__content { + margin-top: $spacing-vertical * 2/3; + margin-bottom: $spacing-vertical * 2/3; + padding: 0 $padding-card-horizontal; +} +.card__subtext { + color: #444; + margin-top: 12px; + font-size: 0.9em; + margin-top: $spacing-vertical * 2/3; + margin-bottom: $spacing-vertical * 2/3; + padding: 0 $padding-card-horizontal; +} +.card__subtext--two-lines { + height: $font-size * 0.9 * $font-line-height * 2; +} +.card-overlay { + position: absolute; + left: 0px; + right: 0px; + top: 0px; + bottom: 0px; + padding: 20px; + background-color: rgba(128, 128, 128, 0.8); + color: #fff; + display: flex; + align-items: center; + font-weight: 600; +} + +.card__media img { + max-width: 100%;; + display: block; + margin-left: auto; + margin-right: auto; +} + +$width-card-small: $spacing-vertical * 12; +.card--small { + width: $width-card-small; +} +.card--small .card__media { + max-height: $width-card-small * 9 / 16; + img { + max-height: $width-card-small * 9 / 16; + } +} + +.card__subtitle { + color: $color-meta-light; + font-size: 0.85em; +} + +.card-series-submit +{ + margin-left: auto; + margin-right: auto; + max-width: 800px; + padding: $spacing-vertical / 2; +} + +.card-row { + > .card { + vertical-align: top; + display: inline-block; + margin-right: $spacing-vertical / 3; + } + + .card-row { + margin-top: $spacing-vertical * 1/3; + } +} +.card-row__header { + margin-bottom: $spacing-vertical / 3; +} \ No newline at end of file diff --git a/ui/scss/component/_file-tile.scss b/ui/scss/component/_file-tile.scss index a5c73a175..1f4b463b8 100644 --- a/ui/scss/component/_file-tile.scss +++ b/ui/scss/component/_file-tile.scss @@ -2,10 +2,10 @@ .file-tile__row { height: $spacing-vertical * 7; -} -.file-tile__row--unavailable { - opacity: 0.5; + .file-price { + float: right; + } } .file-tile__thumbnail { @@ -20,10 +20,6 @@ font-weight: bold; } -.file-tile__cost { - float: right; -} - .file-tile__description { color: #444; margin-top: 12px; diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index db2d168e0..3f22a9af2 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -1,14 +1,88 @@ @import "../global"; -.form-field { - display: inline-block; +.form-row-submit +{ + margin-top: $spacing-vertical; } -.form-field__label { +$height-input: $spacing-vertical * 1.5; + +.form-row__label-row { margin-top: $spacing-vertical * 2/3; margin-bottom: $spacing-vertical * 1/3; line-height: 1; } +.form-row__label-row--prefix { + float: left; + margin-right: 5px; + line-height: $height-input; +} + +.form-field { + display: inline-block; + + input[type="checkbox"], + input[type="radio"] { + cursor: pointer; + } + + textarea, + select, + input[type="text"], + input[type="password"], + input[type="email"], + input[type="number"], + input[type="search"], + input[type="date"] { + @include placeholder { + color: lighten($color-text-dark, 60%); + } + transition: all $transition-standard; + cursor: pointer; + padding-left: 1px; + padding-right: 1px; + box-sizing: border-box; + -webkit-appearance: none; + &[readonly] { + background-color: #bbb; + } + } + + input[type="text"], + input[type="password"], + input[type="email"], + input[type="number"], + input[type="search"], + input[type="date"] { + border-bottom: 2px solid $color-form-border; + line-height: $spacing-vertical - 4; + height: $height-input; + &.form-field__input--error { + border-color: $color-error; + } + } + + textarea:focus, + input[type="text"]:focus, + input[type="password"]:focus, + input[type="email"]:focus, + input[type="number"]:focus, + input[type="search"]:focus, + input[type="date"]:focus { + border-color: $color-primary; + } + + textarea { + border: 2px solid $color-form-border; + } +} + +.form-field__label { + &[for] { cursor: pointer; } + > input[type="checkbox"], input[type="radio"] { + margin-right: 6px; + } +} .form-field__label--error { color: $color-error; @@ -18,8 +92,8 @@ width: 330px; } -.form-field__input-text-number { - width: 50px; +.form-field__input-number { + width: 100px; } .form-field__error, .form-field__helper { diff --git a/ui/scss/component/_modal-page.scss b/ui/scss/component/_modal-page.scss index 5c56e1ad3..2b86c5cad 100644 --- a/ui/scss/component/_modal-page.scss +++ b/ui/scss/component/_modal-page.scss @@ -19,14 +19,16 @@ justify-content: center; align-items: center; + border: 1px solid rgb(204, 204, 204); + background: rgb(255, 255, 255); + overflow: auto; +} + +.modal-page--full { left: 0; right: 0; top: 0; bottom: 0; - - border: 1px solid rgb(204, 204, 204); - background: rgb(255, 255, 255); - overflow: auto; } /* diff --git a/ui/scss/component/_notification-bar.scss b/ui/scss/component/_notification-bar.scss deleted file mode 100644 index 2f9959f94..000000000 --- a/ui/scss/component/_notification-bar.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "../global"; - -.notification-bar { - margin-top: 5px; - margin-right: 10px; -} diff --git a/ui/scss/component/_snack-bar.scss b/ui/scss/component/_snack-bar.scss new file mode 100644 index 000000000..c3df3ab92 --- /dev/null +++ b/ui/scss/component/_snack-bar.scss @@ -0,0 +1,42 @@ +@import "../global"; + +$padding-snack-horizontal: $spacing-vertical; + +.snack-bar { + $height-snack: $spacing-vertical * 2; + $padding-snack-vertical: $spacing-vertical / 4; + + line-height: $height-snack - $padding-snack-vertical * 2; + padding: $padding-snack-vertical $padding-snack-horizontal; + position: fixed; + top: $spacing-vertical; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + min-width: 300px; + max-width: 500px; + background: $color-black-transparent; + color: #f0f0f0; + + display: flex; + justify-content: space-between; + align-items: center; + + border-radius: 2px; + + transition: all $transition-standard; + + z-index: 10000; /*hack to get it over react modal */ +} + +.snack-bar__action { + display: inline-block; + text-transform: uppercase; + color: $color-primary-light; + margin: 0px 0px 0px $padding-snack-horizontal; + min-width: min-content; + &:hover { + text-decoration: underline; + } +} From cbb3da2795d3d304398fd0d924417ee212a50459 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Mon, 10 Apr 2017 10:09:48 -0400 Subject: [PATCH 25/51] rebase fix 1 of n --- ui/js/page/discover.js | 2 +- ui/js/page/wallet.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js index 7751db2db..793362f38 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover.js @@ -72,7 +72,7 @@ var FeaturedCategory = React.createClass({ : '' } : '' } - { this.props.names.map((name) => { return }) } + { this.props.names.map((name) => { return }) }
) } }) diff --git a/ui/js/page/wallet.js b/ui/js/page/wallet.js index fd68345cb..257308c21 100644 --- a/ui/js/page/wallet.js +++ b/ui/js/page/wallet.js @@ -24,12 +24,12 @@ var AddressSection = React.createClass({ event.preventDefault(); } - lbry.wallet_new_address().then((address) => { + lbry.wallet_new_address().then(function(address) { window.localStorage.setItem('wallet_address', address); this.setState({ address: address, }); - }.bind(this)); + }.bind(this)) }, getInitialState: function() { From 029b0b9c3acc5a68c28d5cb36e1de9040a5693f5 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Mon, 10 Apr 2017 14:12:07 -0400 Subject: [PATCH 26/51] more --- ui/js/component/auth.js | 62 +++---- ui/js/component/file-actions.js | 2 +- ui/js/component/file-tile.js | 51 +++++- ui/js/component/form.js | 5 +- ui/js/lbryio.js | 3 - ui/js/page/publish.js | 283 +++++++++++++++-------------- ui/js/page/settings.js | 19 +- ui/js/page/wallet.js | 2 +- ui/scss/component/_card.scss | 3 +- ui/scss/component/_form-field.scss | 35 +++- 10 files changed, 265 insertions(+), 200 deletions(-) diff --git a/ui/js/component/auth.js b/ui/js/component/auth.js index 83edd2c9f..bf3fb4232 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth.js @@ -157,7 +157,7 @@ export const AuthOverlay = React.createClass({ getInitialState: function() { return { - stage: "welcome", + stage: "pending", stageProps: {} }; }, @@ -167,36 +167,36 @@ export const AuthOverlay = React.createClass({ }); }, componentWillMount: function() { - // lbryio.authenticate().then(function(user) { - // if (!user.HasVerifiedEmail) { //oops I fucked this up - // this.setState({ - // stage: "email", - // stageProps: { - // onEmailSaved: function() { - // this.setState({ - // stage: "confirm", - // stageProps: { - // onEmailConfirmed: function() { this.setState({ stage: "welcome"}) }.bind(this) - // } - // }) - // }.bind(this) - // } - // }) - // } else { - // this.endAuth() - // } - // }.bind(this)).catch((err) => { - // this.setState({ - // stage: "error", - // stageProps: { errorText: err.message } - // }) - // document.dispatchEvent(new CustomEvent('unhandledError', { - // detail: { - // message: err.message, - // data: err.stack - // } - // })); - // }) + lbryio.authenticate().then(function(user) { + if (!user.HasVerifiedEmail) { //oops I fucked this up + this.setState({ + stage: "email", + stageProps: { + onEmailSaved: function() { + this.setState({ + stage: "confirm", + stageProps: { + onEmailConfirmed: function() { this.setState({ stage: "welcome"}) }.bind(this) + } + }) + }.bind(this) + } + }) + } else { + this.endAuth() + } + }.bind(this)).catch((err) => { + this.setState({ + stage: "error", + stageProps: { errorText: err.message } + }) + document.dispatchEvent(new CustomEvent('unhandledError', { + detail: { + message: err.message, + data: err.stack + } + })); + }) }, render: function() { if (!this.state.stage || lbryio.user && lbryio.user.HasVerifiedEmail) { diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index 715133bd8..410d14d66 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -294,7 +294,7 @@ export let FileActions = React.createClass({ ? :
-
This file is not currently available.
+
Content unavailable.
diff --git a/ui/js/component/file-tile.js b/ui/js/component/file-tile.js index bb9483374..463269c05 100644 --- a/ui/js/component/file-tile.js +++ b/ui/js/component/file-tile.js @@ -140,6 +140,7 @@ export let FileTileStream = React.createClass({ { !this.props.hidePrice ? : null} +<<<<<<< dd3f3ec4d00066633b136925111bae7193b3c6a8

@@ -159,9 +160,25 @@ export let FileTileStream = React.createClass({ {isConfirmed ? metadata.description : This file is pending confirmation.} +======= + +

+ + + {title} +>>>>>>> more -

-

+ + + + +

+ + {isConfirmed + ? metadata.description + : This file is pending confirmation.} + +

{this.state.showNsfwHelp @@ -180,9 +197,12 @@ export let FileTileStream = React.createClass({ export let FileCardStream = React.createClass({ _fileInfoSubscribeId: null, _isMounted: null, + _metadata: null, + propTypes: { - metadata: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]), + uri: React.PropTypes.string, + claimInfo: React.PropTypes.object, outpoint: React.PropTypes.string, hideOnRemove: React.PropTypes.bool, hidePrice: React.PropTypes.bool, @@ -207,6 +227,11 @@ export let FileCardStream = React.createClass({ this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); } }, + componentWillMount: function() { + const {value: {stream: {metadata, source: {contentType}}}} = this.props.claimInfo; + this._metadata = metadata; + this._contentType = contentType; + }, componentWillUnmount: function() { if (this._fileInfoSubscribeId) { lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); @@ -220,7 +245,7 @@ export let FileCardStream = React.createClass({ } }, handleMouseOver: function() { - if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { + if (this.props.obscureNsfw && this.props.metadata && this._metadata.nsfw) { this.setState({ showNsfwHelp: true, }); @@ -238,11 +263,16 @@ export let FileCardStream = React.createClass({ return null; } +<<<<<<< dd3f3ec4d00066633b136925111bae7193b3c6a8 const lbryUri = uri.normalizeLbryUri(this.props.uri); const metadata = this.props.metadata; +======= + const metadata = this._metadata; +>>>>>>> more const isConfirmed = typeof metadata == 'object'; const title = isConfirmed ? metadata.title : lbryUri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; + console.log(this.props); return (
@@ -254,10 +284,15 @@ export let FileCardStream = React.createClass({ +<<<<<<< dd3f3ec4d00066633b136925111bae7193b3c6a8
+======= + +
+>>>>>>> more
@@ -325,7 +360,11 @@ export let FileTile = React.createClass({ const {txid, nout, has_signature, signature_is_valid, value: {stream: {metadata, source: {contentType}}}} = this.state.claimInfo; - return ; + return + return this.props.displayStyle == 'card' ? + : + ; } }); diff --git a/ui/js/component/form.js b/ui/js/component/form.js index b33fcddd5..ddc4ee350 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -51,7 +51,7 @@ export let FormField = React.createClass({ if (this.props.type == 'checkbox') { return this.refs.field.checked; } else if (this.props.type == 'file') { - return this.refs.field.files[0].path; + return this.refs.field.files.length && this.refs.field.files[0].path; } else { return this.refs.field.value; } @@ -121,6 +121,9 @@ export let FormRow = React.createClass({ getValue: function() { return this.refs.field.getValue(); }, + getSelectedElement: function() { + return this.refs.field.getSelectedElement(); + }, render: function() { const fieldProps = Object.assign({}, this.props), elementId = formFieldId(), diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 9c6d37f03..06dbd46d9 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -62,7 +62,6 @@ lbryio.call = function(resource, action, params={}, method='get') { })); } } else { - console.info(`${resource}.${action} response data:`, response); resolve(response.data); } }); @@ -75,11 +74,9 @@ lbryio.call = function(resource, action, params={}, method='get') { const fullParams = {app_id: lbryio._accessToken, ...params}; if (method == 'get') { - console.info('GET ', CONNECTION_STRING + resource + '/' + action, ' | params:', fullParams); xhr.open('get', CONNECTION_STRING + resource + '/' + action + '?' + querystring.stringify(fullParams), true); xhr.send(); } else if (method == 'post') { - console.info('POST ', CONNECTION_STRING + resource + '/' + action, '| params: ', fullParams); xhr.open('post', CONNECTION_STRING + resource + '/' + action, true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.send(querystring.stringify(fullParams)); diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 4f28dc567..d1d52894b 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -6,7 +6,7 @@ import {Link} from '../component/link.js'; import Modal from '../component/modal.js'; var PublishPage = React.createClass({ - _requiredFields: ['name', 'bid', 'meta_title', 'meta_author', 'meta_license', 'meta_description'], + _requiredFields: ['name', 'bid', 'meta_title'], _updateChannelList: function(channel) { // Calls API to update displayed list of channels. If a channel name is provided, will select @@ -85,7 +85,9 @@ var PublishPage = React.createClass({ publishArgs.file_path = this.refs.file.getValue(); } + console.log(publishArgs); lbry.publish(publishArgs, (message) => { + console.log(message); this.handlePublishStarted(); }, null, (error) => { this.handlePublishError(error); @@ -112,6 +114,7 @@ var PublishPage = React.createClass({ rawName: '', name: '', bid: '', + hasFile: false, feeAmount: '', feeCurrency: 'USD', channel: 'anonymous', @@ -237,7 +240,7 @@ var PublishPage = React.createClass({ isFee: feeEnabled }); }, - handeLicenseChange: function(event) { + handleLicenseChange: function(event) { var licenseType = event.target.options[event.target.selectedIndex].getAttribute('data-license-type'); var newState = { copyrightChosen: licenseType == 'copyright', @@ -245,8 +248,7 @@ var PublishPage = React.createClass({ }; if (licenseType == 'copyright') { - var author = this.refs.meta_author.getValue(); - newState.copyrightNotice = 'Copyright ' + (new Date().getFullYear()) + (author ? ' ' + author : ''); + newState.copyrightNotice = 'All rights reserved.' } this.setState(newState); @@ -277,7 +279,7 @@ var PublishPage = React.createClass({ const newChannelName = (event.target.value.startsWith('@') ? event.target.value : '@' + event.target.value); if (newChannelName.length > 1 && !lbry.nameIsValid(newChannelName.substr(1), false)) { - this.refs.newChannelName.showAdvice('LBRY channel names must contain only letters, numbers and dashes.'); + this.refs.newChannelName.showError('LBRY channel names must contain only letters, numbers and dashes.'); return; } @@ -292,7 +294,7 @@ var PublishPage = React.createClass({ }, handleCreateChannelClick: function (event) { if (this.state.newChannelName.length < 5) { - this.refs.newChannelName.showAdvice('LBRY channel names must be at least 4 characters in length.'); + this.refs.newChannelName.showError('LBRY channel names must be at least 4 characters in length.'); return; } @@ -311,7 +313,7 @@ var PublishPage = React.createClass({ }, 5000); }, (error) => { // TODO: better error handling - this.refs.newChannelName.showAdvice('Unable to create channel due to an internal error.'); + this.refs.newChannelName.showError('Unable to create channel due to an internal error.'); this.setState({ creatingChannel: false, }); @@ -334,7 +336,27 @@ var PublishPage = React.createClass({ }, componentDidUpdate: function() { }, - // Also getting a type warning here too + onFileChange: function() { + if (this.refs.file.getValue()) { + this.setState({ hasFile: true }) + } else { + this.setState({ hasFile: false }) + } + }, + getNameBidHelpText: function() { + if (!this.state.name) { + return "Select a URL for this publish."; + } else if (!this.state.nameResolved) { + return "This URL is unused."; + } else if (this.state.myClaimExists) { + return "You have already used this URL. Publishing to it again will update your previous publish." + } else if (this.state.topClaimValue) { + return A deposit of at least {this.state.topClaimValue} {this.state.topClaimValue == 1 ? 'credit' : 'credits'} + is required to win {this.state.name}. However, you can still get a perminent URL for any amount. + } else { + return ''; + } + }, render: function() { if (this.state.channels === null) { return null; @@ -345,153 +367,136 @@ var PublishPage = React.createClass({
-

LBRY Name

+

Content

+
+ What are you publishing? +
What LBRY name would you like to claim for this file? .
)} /> - { - (!this.state.name - ? null - : (!this.state.nameResolved - ? The name {this.state.name} is available. - : (this.state.myClaimExists - ? You already have a claim on the name {this.state.name}. You can use this page to update your claim. - : The name {this.state.name} is currently claimed for {this.state.topClaimValue} {this.state.topClaimValue == 1 ? 'credit' : 'credits'}.))) - } + +
+ { !this.state.hasFile ? '' : +
+ + + + + + + + + + + + + + + + + +
} +
+ +
+
+

Access

+
+ How much does this content cost ? +
+
+
+
+ +
+ { this.handleFeePrefChange(false) } } checked={!this.state.isFee} /> + { this.handleFeePrefChange(true) } } checked={this.state.isFee} /> + + + + + + + { this.state.isFee ? +
+ If you choose to price this content in dollars, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase. +
: '' } + + + + + + + + + + + + + {this.state.copyrightChosen + ? + : null} + {this.state.otherLicenseChosen ? + + : null} + {this.state.otherLicenseChosen ? + + : null}
-

Channel

-
- +
+

Identity

+
+ Who created this content? +
+
+
+ {this.state.channels.map(({name}) => )} - - - {this.state.channel == 'new' - ?
- - - -
- : null} -
What channel would you like to publish this file under?
+ +
+ {this.state.channel == 'new' ? +
+ { this.refs.newChannelName = newChannelName }} + value={this.state.newChannelName} /> + +
+ +
+
+ : null}
+
-

Choose File

+
+

Address

+
Where should this content permanently reside?
+
- - { this.state.myClaimExists ?
If you don't choose a file, the file from your existing claim will be used.
: null } -
-
- -
-

Bid Amount

-
- Credits -
How much would you like to bid for this name? - { !this.state.nameResolved ? Since this name is not currently resolved, you may bid as low as you want, but higher bids help prevent others from claiming your name. - : (this.state.topClaimIsMine ? You currently control this name with a bid of {this.state.myClaimValue} {this.state.myClaimValue == 1 ? 'credit' : 'credits'}. - : (this.state.myClaimExists ? You have a non-winning bid on this name for {this.state.myClaimValue} {this.state.myClaimValue == 1 ? 'credit' : 'credits'}. - To control this name, you'll need to increase your bid to more than {this.state.topClaimValue} {this.state.topClaimValue == 1 ? 'credit' : 'credits'}. - : You must bid over {this.state.topClaimValue} {this.state.topClaimValue == 1 ? 'credit' : 'credits'} to claim this name.)) } -
-
-
- -
-

Fee

-
- - -
-

How much would you like to charge for this file?

- If you choose to price this content in dollars, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase. -
-
-
- - -
-

Your Content

- -
- -
-
- -
-
- - - - - - - - - - - - -
- {this.state.copyrightChosen - ?
- -
- : null} - {this.state.otherLicenseChosen - ?
- -
- : null} - {this.state.otherLicenseChosen - ?
- -
- : null} - -
- - - - - - - - - -
-
- -
-
- -
-
- - - -
-

Additional Content Information (Optional)

-
- + Select a URL for this publish. .
)} />
+ { this.state.rawName ? +
+ +
: '' }
diff --git a/ui/js/page/settings.js b/ui/js/page/settings.js index c278741b4..d6df42cfb 100644 --- a/ui/js/page/settings.js +++ b/ui/js/page/settings.js @@ -95,12 +95,12 @@ var SettingsPage = React.createClass({

Max Upload

- -

Max Download

- + + { /* + */ }

Content

-
+

Share Diagnostic Data

-
+
Send Credits
- +
diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index af42624b2..c2c9c2a9f 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -95,8 +95,9 @@ $width-card-small: $spacing-vertical * 12; } .card__subtitle { - color: $color-meta-light; + color: $color-help; font-size: 0.85em; + line-height: $font-line-height * 1 / 0.85; } .card-series-submit diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index 3f22a9af2..14744326b 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -1,21 +1,21 @@ @import "../global"; +$width-input-border: 2px; + .form-row-submit { margin-top: $spacing-vertical; } -$height-input: $spacing-vertical * 1.5; - .form-row__label-row { margin-top: $spacing-vertical * 2/3; margin-bottom: $spacing-vertical * 1/3; line-height: 1; + font-size: 0.9em; } .form-row__label-row--prefix { float: left; margin-right: 5px; - line-height: $height-input; } .form-field { @@ -26,8 +26,19 @@ $height-input: $spacing-vertical * 1.5; cursor: pointer; } + select { + transition: outline $transition-standard; + cursor: pointer; + box-sizing: border-box; + padding-left: 5px; + padding-right: 5px; + height: $spacing-vertical; + &:focus { + outline: $width-input-border solid $color-primary; + } + } + textarea, - select, input[type="text"], input[type="password"], input[type="email"], @@ -54,9 +65,10 @@ $height-input: $spacing-vertical * 1.5; input[type="number"], input[type="search"], input[type="date"] { - border-bottom: 2px solid $color-form-border; - line-height: $spacing-vertical - 4; - height: $height-input; + border-bottom: $width-input-border solid $color-form-border; + line-height: 1px; + padding-top: $spacing-vertical * 1/3; + padding-bottom: $spacing-vertical * 1/3; &.form-field__input--error { border-color: $color-error; } @@ -73,7 +85,7 @@ $height-input: $spacing-vertical * 1.5; } textarea { - border: 2px solid $color-form-border; + border: $width-input-border solid $color-form-border; } } @@ -93,7 +105,12 @@ $height-input: $spacing-vertical * 1.5; } .form-field__input-number { - width: 100px; + width: 70px; + text-align: right; +} + +.form-field__input-textarea { + width: 330px; } .form-field__error, .form-field__helper { From 0ba69ff32b4f6355c4c036338c27d583a8e1435c Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 11 Apr 2017 12:03:56 -0400 Subject: [PATCH 27/51] stashed cleanup --- ui/js/page/help.js | 98 ++++++++++++++++++++++++------------------- ui/js/page/publish.js | 5 +-- 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/ui/js/page/help.js b/ui/js/page/help.js index 632c3abd0..99e6ad0d7 100644 --- a/ui/js/page/help.js +++ b/ui/js/page/help.js @@ -51,56 +51,68 @@ var HelpPage = React.createClass({ return (
-

Read the FAQ

-

Our FAQ answers many common questions.

-

+
+

Read the FAQ

+
+
+

Our FAQ answers many common questions.

+

+
-

Get Live Help

-

- Live help is available most hours in the #help channel of our Slack chat room. -

-

- -

+
+

Get Live Help

+
+
+

+ Live help is available most hours in the #help channel of our Slack chat room. +

+

+ +

+
-

Report a Bug

-

Did you find something wrong?

-

-
Thanks! LBRY is made by its users.
+

Report a Bug

+
+

Did you find something wrong?

+

+
Thanks! LBRY is made by its users.
+
{!ver ? null :
-

About

- {ver.lbrynet_update_available || ver.lbryum_update_available ? -

A newer version of LBRY is available.

- :

Your copy of LBRY is up to date.

- } - - - - - - - - - - - - - - - - - - - - - - - -
daemon (lbrynet){ver.lbrynet_version}
wallet (lbryum){ver.lbryum_version}
interface{uiVersion}
Platform{platform}
Installation ID{this.state.lbryId}
+

About

+
+ {ver.lbrynet_update_available || ver.lbryum_update_available ? +

A newer version of LBRY is available.

+ :

Your copy of LBRY is up to date.

+ } + + + + + + + + + + + + + + + + + + + + + + + +
daemon (lbrynet){ver.lbrynet_version}
wallet (lbryum){ver.lbryum_version}
interface{uiVersion}
Platform{platform}
Installation ID{this.state.lbryId}
+
}
diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index d1d52894b..1b41aa3b5 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -61,7 +61,7 @@ var PublishPage = React.createClass({ var metadata = {}; } - for (let metaField of ['title', 'author', 'description', 'thumbnail', 'license', 'license_url', 'language', 'nsfw']) { + for (let metaField of ['title', 'description', 'thumbnail', 'license', 'license_url', 'language', 'nsfw']) { var value = this.refs['meta_' + metaField].getValue(); if (value !== '') { metadata[metaField] = value; @@ -113,7 +113,7 @@ var PublishPage = React.createClass({ channels: null, rawName: '', name: '', - bid: '', + bid: 0.01, hasFile: false, feeAmount: '', feeCurrency: 'USD', @@ -491,7 +491,6 @@ var PublishPage = React.createClass({ type="number" step="0.01" label="Deposit" - defaultValue="0.01" onChange={this.handleBidChange} value={this.state.bid} placeholder={this.state.nameResolved ? this.state.topClaimValue + 10 : 100} From 0313ba941addea7eac0aa22246934ad764060330 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 11 Apr 2017 22:01:45 -0400 Subject: [PATCH 28/51] approaching acceptable --- ui/js/app.js | 9 +- ui/js/component/channel-indicator.js | 19 ++- ui/js/component/common.js | 13 +- ui/js/component/file-tile.js | 139 +++++++--------------- ui/js/lbry.js | 7 +- ui/js/page/discover.js | 21 ++-- ui/js/page/publish.js | 2 - ui/js/page/show.js | 5 +- ui/scss/_canvas.scss | 21 +++- ui/scss/_gui.scss | 7 -- ui/scss/component/_card.scss | 37 ++++-- ui/scss/component/_channel-indicator.scss | 2 +- ui/scss/component/_file-tile.scss | 2 +- 13 files changed, 126 insertions(+), 158 deletions(-) diff --git a/ui/js/app.js b/ui/js/app.js index 424fb8533..e18fdf25e 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -106,6 +106,8 @@ var App = React.createClass({ if (target.matches('a[href^="?"]')) { event.preventDefault(); if (this._isMounted) { + history.pushState({}, document.title, target.getAttribute('href')); + this.registerHistoryPop(); this.setState(this.getViewingPageAndArgs(target.getAttribute('href'))); } } @@ -147,6 +149,11 @@ var App = React.createClass({ componentWillUnmount: function() { this._isMounted = false; }, + registerHistoryPop: function() { + window.addEventListener("popstate", function() { + this.setState(this.getViewingPageAndArgs(location.pathname)); + }.bind(this)); + }, handleUpgradeClicked: function() { // Make a new directory within temp directory so the filename is guaranteed to be available const dir = fs.mkdtempSync(app.getPath('temp') + require('path').sep); @@ -261,7 +268,7 @@ var App = React.createClass({ case 'start': return ; case 'rewards': - return ; + return ; case 'wallet': case 'send': case 'receive': diff --git a/ui/js/component/channel-indicator.js b/ui/js/component/channel-indicator.js index 37897cbcd..2f30d5755 100644 --- a/ui/js/component/channel-indicator.js +++ b/ui/js/component/channel-indicator.js @@ -3,20 +3,18 @@ import lbry from '../lbry.js'; import uri from '../uri.js'; import {Icon} from './common.js'; -const ChannelIndicator = React.createClass({ +const UriIndicator = React.createClass({ propTypes: { uri: React.PropTypes.string.isRequired, hasSignature: React.PropTypes.bool.isRequired, signatureIsValid: React.PropTypes.bool, }, render: function() { - if (!this.props.hasSignature) { - return null; - } const uriObj = uri.parseLbryUri(this.props.uri); - if (!uriObj.isChannel) { - return null; + + if (!this.props.hasSignature || !uriObj.isChannel) { + return Anonymous; } const channelUriObj = Object.assign({}, uriObj); @@ -25,7 +23,6 @@ const ChannelIndicator = React.createClass({ let icon, modifier; if (this.props.signatureIsValid) { - icon = 'icon-check-circle'; modifier = 'valid'; } else { icon = 'icon-times-circle'; @@ -33,11 +30,13 @@ const ChannelIndicator = React.createClass({ } return ( - by {channelUri} {' '} - + {channelUri} {' '} + { !this.props.signatureIsValid ? + : + '' } ); } }); -export default ChannelIndicator; \ No newline at end of file +export default UriIndicator; \ No newline at end of file diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 9e163476c..98dbd45eb 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -54,15 +54,6 @@ export let BusyMessage = React.createClass({ } }); -var creditAmountStyle = { - color: '#216C2A', - fontWeight: 'bold', - fontSize: '0.8em' -}, estimateStyle = { - fontSize: '0.8em', - color: '#aaa', -}; - export let CurrencySymbol = React.createClass({ render: function() { return LBC; } }); @@ -76,8 +67,8 @@ export let CreditAmount = React.createClass({ var formattedAmount = lbry.formatCredits(this.props.amount, this.props.precision ? this.props.precision : 1); return ( - {formattedAmount} {parseFloat(formattedAmount) == 1.0 ? 'credit' : 'credits'} - { this.props.isEstimate ? (est) : null } + {formattedAmount} {parseFloat(formattedAmount) == 1.0 ? 'credit' : 'credits'} + { this.props.isEstimate ? * : null } ); } diff --git a/ui/js/component/file-tile.js b/ui/js/component/file-tile.js index 463269c05..199f341bd 100644 --- a/ui/js/component/file-tile.js +++ b/ui/js/component/file-tile.js @@ -4,7 +4,7 @@ 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 ChannelIndicator from '../component/channel-indicator.js'; +import UriIndicator from '../component/channel-indicator.js'; let FilePrice = React.createClass({ _isMounted: false, @@ -41,15 +41,10 @@ let FilePrice = React.createClass({ }, render: function() { - if (this.state.cost === null) - { - return null; - } - return ( - - - + this.state.cost !== null ? + : + ... ); } }); @@ -117,14 +112,10 @@ export let FileTileStream = React.createClass({ } }, render: function() { - console.log('rendering.') if (this.state.isHidden) { - console.log('hidden, so returning null') return null; } - console.log("inside FileTileStream. metadata is", this.props.metadata) - const lbryUri = uri.normalizeLbryUri(this.props.uri); const metadata = this.props.metadata; const isConfirmed = typeof metadata == 'object'; @@ -140,7 +131,6 @@ export let FileTileStream = React.createClass({ { !this.props.hidePrice ? : null} -<<<<<<< dd3f3ec4d00066633b136925111bae7193b3c6a8

@@ -158,27 +148,11 @@ export let FileTileStream = React.createClass({

{isConfirmed - ? metadata.description - : This file is pending confirmation.} -======= -

-

- - - {title} ->>>>>>> more + ? metadata.description + : This file is pending confirmation.} - -

- - -

- - {isConfirmed - ? metadata.description - : This file is pending confirmation.} - -

+

+
{this.state.showNsfwHelp @@ -218,7 +192,8 @@ export let FileCardStream = React.createClass({ getDefaultProps: function() { return { obscureNsfw: !lbry.getClientSetting('showNsfw'), - hidePrice: false + hidePrice: false, + hasSignature: false, } }, componentDidMount: function() { @@ -227,11 +202,6 @@ export let FileCardStream = React.createClass({ this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); } }, - componentWillMount: function() { - const {value: {stream: {metadata, source: {contentType}}}} = this.props.claimInfo; - this._metadata = metadata; - this._contentType = contentType; - }, componentWillUnmount: function() { if (this._fileInfoSubscribeId) { lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); @@ -245,79 +215,56 @@ export let FileCardStream = React.createClass({ } }, handleMouseOver: function() { - if (this.props.obscureNsfw && this.props.metadata && this._metadata.nsfw) { - this.setState({ - showNsfwHelp: true, - }); - } + this.setState({ + hovered: true, + }); }, handleMouseOut: function() { - if (this.state.showNsfwHelp) { - this.setState({ - showNsfwHelp: false, - }); - } + this.setState({ + hovered: false, + }); }, render: function() { if (this.state.isHidden) { return null; } -<<<<<<< dd3f3ec4d00066633b136925111bae7193b3c6a8 const lbryUri = uri.normalizeLbryUri(this.props.uri); const metadata = this.props.metadata; -======= - const metadata = this._metadata; ->>>>>>> more const isConfirmed = typeof metadata == 'object'; const title = isConfirmed ? metadata.title : lbryUri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; - console.log(this.props); + const primaryUrl = '?watch=' + lbryUri; return ( -
+
->>>>>>> more -
- -
- { !this.props.hidePrice - ? +
+ + {this.state.showNsfwHelp && this.state.hovered + ?
+

+ This content is Not Safe For Work. + To view adult content, please change your . +

+
: null} -
- - {isConfirmed - ? metadata.description - : This file is pending confirmation.} - -
-
- -
- {this.state.showNsfwHelp - ?
-

- This content is Not Safe For Work. - To view adult content, please change your . -

-
- : null}
); } @@ -360,7 +307,7 @@ export let FileTile = React.createClass({ const {txid, nout, has_signature, signature_is_valid, value: {stream: {metadata, source: {contentType}}}} = this.state.claimInfo; - return + return this.props.displayStyle == 'card' ? : diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 8fbfdb04d..51abc311b 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -250,10 +250,6 @@ lbry.getCostInfo = function(lbryUri, callback, errorCallback) { * - includes_data: Boolean; indicates whether or not the data fee info * from Lighthouse is included. */ - if (!name) { - throw new Error(`Name required.`); - } - function getCostWithData(name, size, callback, errorCallback) { lbry.stream_cost_estimate({name, size}).then((cost) => { callback({ @@ -507,7 +503,7 @@ lbry.stop = function(callback) { lbry.fileInfo = {}; lbry._subscribeIdCount = 0; lbry._fileInfoSubscribeCallbacks = {}; -lbry._fileInfoSubscribeInterval = 5000; +lbry._fileInfoSubscribeInterval = 500000; lbry._balanceSubscribeCallbacks = {}; lbry._balanceSubscribeInterval = 5000; lbry._removedFiles = []; @@ -519,6 +515,7 @@ lbry._updateClaimOwnershipCache = function(claimId) { return match || claimInfo.claim_id == claimId; }); }); + }; lbry._updateFileInfoSubscribers = function(outpoint) { diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js index 793362f38..b3d427cfe 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover.js @@ -65,7 +65,7 @@ const communityCategoryToolTipText = ('Community Content is a public space where var FeaturedCategory = React.createClass({ render: function() { - return (
+ return (
{ this.props.category ?

{this.props.category} { this.props.category == "community" ? @@ -80,21 +80,28 @@ var FeaturedCategory = React.createClass({ var FeaturedContent = React.createClass({ getInitialState: function() { return { - featuredNames: {}, + featuredUris: {}, }; }, componentWillMount: function() { - lbryio.call('discover', 'list', { version: "early-access" } ).then((featuredNames) => { - this.setState({ featuredNames: featuredNames }); + lbryio.call('discover', 'list', { version: "early-access" } ).then(({Categories, Uris}) => { + let featuredUris = {} + Categories.forEach((category) => { + if (Uris[category] && Uris[category].length) { + featuredUris[category] = Uris[category] + } + }) + this.setState({ featuredUris: featuredUris }); }); }, render: function() { - console.log(this.state.featuredNames); return (
{ - Object.keys(this.state.featuredNames).map(function(category) { - return + Object.keys(this.state.featuredUris).map(function(category) { + return this.state.featuredUris[category].length ? + : + ''; }.bind(this)) }
diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 1b41aa3b5..a220cce5e 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -373,8 +373,6 @@ var PublishPage = React.createClass({

- What LBRY name would you like to claim for this file? .
)} /> diff --git a/ui/js/page/show.js b/ui/js/page/show.js index 49aff7569..7197390d2 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -88,8 +88,9 @@ var FormatsSection = React.createClass({ return (
+ { this.props.metadata.thumbnail ?
: '' } +

{this.props.metadata.title}

{this.props.uri}
-

{this.props.metadata.title}

{/* 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){ */} @@ -141,7 +142,6 @@ var ShowPage = React.createClass({ return (
-
{this.state.uriLookupComplete ? ( ) : ( @@ -150,7 +150,6 @@ var ShowPage = React.createClass({ There is no content available at {this._uri}. If you reached this page from a link within the LBRY interface, please . Thanks!
)} -
); } }); diff --git a/ui/scss/_canvas.scss b/ui/scss/_canvas.scss index 300ecb75b..ac5712240 100644 --- a/ui/scss/_canvas.scss +++ b/ui/scss/_canvas.scss @@ -11,7 +11,7 @@ body line-height: $font-line-height; } -$drawer-width: 240px; +$drawer-width: 220px; #drawer { @@ -39,12 +39,8 @@ $drawer-width: 240px; .badge { float: right; - background: $color-money; - display: inline-block; - padding: 2px; - color: white; margin-top: $spacing-vertical * 0.25 - 2; - border-radius: 2px; + background: $color-money; } } .drawer-item-selected @@ -53,6 +49,19 @@ $drawer-width: 240px; color: $color-primary; } } +.badge +{ + background: $color-money; + display: inline-block; + padding: 2px; + color: white; + border-radius: 2px; +} +.credit-amount +{ + font-weight: bold; + color: $color-money; +} #drawer-handle { padding: $spacing-vertical / 2; diff --git a/ui/scss/_gui.scss b/ui/scss/_gui.scss index 6c97b5fbc..f1cf53c40 100644 --- a/ui/scss/_gui.scss +++ b/ui/scss/_gui.scss @@ -52,13 +52,6 @@ section } */ -main h1 { - font-size: 2.0em; - margin-bottom: $spacing-vertical; - margin-top: $spacing-vertical*2; - font-family: 'Raleway', sans-serif; -} - h2 { font-size: 1.75em; } diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index c2c9c2a9f..608b49007 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -76,22 +76,36 @@ $padding-card-horizontal: $spacing-vertical * 2/3; font-weight: 600; } -.card__media img { - max-width: 100%;; +$card-link-scaling: 1.1; +.card__link { display: block; - margin-left: auto; - margin-right: auto; +} +.card--link:hover { + position: relative; + z-index: 1; + box-shadow: $focus-box-shadow; + transform: scale($card-link-scaling); + transform-origin: 50% 50%; + overflow-x: visible; + overflow-y: visible; +} + +.card__media { + background-size: cover; + background-repeat: no-repeat; + background-position: 50% 50%; } $width-card-small: $spacing-vertical * 12; +$height-card-small: $spacing-vertical * 15; + .card--small { width: $width-card-small; + overflow-x: hidden; + white-space: normal; } .card--small .card__media { - max-height: $width-card-small * 9 / 16; - img { - max-height: $width-card-small * 9 / 16; - } + height: $width-card-small * 9 / 16; } .card__subtitle { @@ -118,6 +132,13 @@ $width-card-small: $spacing-vertical * 12; margin-top: $spacing-vertical * 1/3; } } +.card-row--small { + overflow-x: auto; + overflow-y: hidden; + white-space: nowrap; + padding-left: 20px; + margin-left: -20px; /*hacky way to give space for hover */ +} .card-row__header { margin-bottom: $spacing-vertical / 3; } \ No newline at end of file diff --git a/ui/scss/component/_channel-indicator.scss b/ui/scss/component/_channel-indicator.scss index 06446e23f..52a0baed6 100644 --- a/ui/scss/component/_channel-indicator.scss +++ b/ui/scss/component/_channel-indicator.scss @@ -1,5 +1,5 @@ @import "../global"; .channel-indicator__icon--invalid { - color: #b01c2e; + color: $color-error; } diff --git a/ui/scss/component/_file-tile.scss b/ui/scss/component/_file-tile.scss index 1f4b463b8..a3dc30d28 100644 --- a/ui/scss/component/_file-tile.scss +++ b/ui/scss/component/_file-tile.scss @@ -3,7 +3,7 @@ .file-tile__row { height: $spacing-vertical * 7; - .file-price { + .credit-amount { float: right; } } From cb4af24cd748b10843388a5e5127cddebb795ae2 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 12 Apr 2017 10:55:19 -0400 Subject: [PATCH 29/51] more clean up and fixes --- ui/js/app.js | 3 - ui/js/component/form.js | 44 ++++++----- ui/js/page/email.js | 36 --------- ui/js/page/publish.js | 79 ++++++++++++++------ ui/js/page/settings.js | 116 ++++++++++++++++------------- ui/js/page/wallet.js | 2 +- ui/scss/component/_card.scss | 2 +- ui/scss/component/_file-tile.scss | 7 +- ui/scss/component/_form-field.scss | 20 ++++- 9 files changed, 169 insertions(+), 140 deletions(-) delete mode 100644 ui/js/page/email.js diff --git a/ui/js/app.js b/ui/js/app.js index e18fdf25e..6126901e7 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -2,7 +2,6 @@ import React from 'react'; import {Line} from 'rc-progress'; import lbry from './lbry.js'; -import EmailPage from './page/email.js'; import SettingsPage from './page/settings.js'; import HelpPage from './page/help.js'; import WatchPage from './page/watch.js'; @@ -277,8 +276,6 @@ var App = React.createClass({ return ; case 'publish': return ; - case 'email': - return ; case 'developer': return ; case 'discover': diff --git a/ui/js/component/form.js b/ui/js/component/form.js index ddc4ee350..c24bd7b92 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -15,6 +15,8 @@ export let FormField = React.createClass({ propTypes: { type: React.PropTypes.string.isRequired, + prefix: React.PropTypes.string, + postfix: React.PropTypes.string, hasError: React.PropTypes.bool }, getInitialState: function() { @@ -41,9 +43,6 @@ export let FormField = React.createClass({ errorMessage: text, }); }, - showRequiredError: function() { - this.showError(this._fieldRequiredText); - }, focus: function() { this.refs.field.focus(); }, @@ -51,7 +50,7 @@ export let FormField = React.createClass({ if (this.props.type == 'checkbox') { return this.refs.field.checked; } else if (this.props.type == 'file') { - return this.refs.field.files.length && this.refs.field.files[0].path; + return !!(this.refs.field.files.length && this.refs.field.files[0].path); } else { return this.refs.field.value; } @@ -69,6 +68,9 @@ export let FormField = React.createClass({ delete otherProps.type; delete otherProps.label; delete otherProps.hasError; + delete otherProps.className; + delete otherProps.postfix; + delete otherProps.prefix; const element = ; return
+ { this.props.prefix ? {this.props.prefix} : '' } { renderElementInsideLabel ? : element } - { isError ?
{this.state.errorMessage}
: '' } + : + element } + { this.props.postfix ? {this.props.postfix} : '' } + { isError && this.state.errorMessage ?
{this.state.errorMessage}
: '' }
- return ( - this.props.row ? -
{field}
: - field - ); } }) export let FormRow = React.createClass({ + _fieldRequiredText: 'This field is required', propTypes: { label: React.PropTypes.string, // helper: React.PropTypes.html, }, - getValue: function() { - if (this.props.type == 'checkbox') { - return this.refs.field.checked; - } else if (this.props.type == 'file') { - return this.refs.field.files[0].path; - } else { - return this.refs.field.value; - } - }, getInitialState: function() { return { isError: false, @@ -118,12 +110,24 @@ export let FormRow = React.createClass({ errorMessage: text, }); }, + showRequiredError: function() { + this.showError(this._fieldRequiredText); + }, + clearError: function(text) { + this.setState({ + isError: false, + errorMessage: '' + }); + }, getValue: function() { return this.refs.field.getValue(); }, getSelectedElement: function() { return this.refs.field.getSelectedElement(); }, + focus: function() { + this.refs.field.focus(); + }, render: function() { const fieldProps = Object.assign({}, this.props), elementId = formFieldId(), diff --git a/ui/js/page/email.js b/ui/js/page/email.js deleted file mode 100644 index 76b031737..000000000 --- a/ui/js/page/email.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import lbryio from '../lbryio.js'; -import {getLocal, setLocal} from '../utils.js'; -import {FormField} from '../component/form.js' -import {Link} from '../component/link.js' -import rewards from '../rewards.js'; - -const EmailPage = React.createClass({ - handleSubmit: function(event) { - if (event !== undefined) { - event.preventDefault(); - } - - if (!this.state.email) { - this._emailField.showRequiredError(); - } - }, - componentWillMount: function() { - this._getRewardType(); - }, - render: function() { - return ( -
-
-

Verify your Email Address

- -
-
- -
-
- ); - } -}); - -export default EmailPage; diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index a220cce5e..7ec3ebd0c 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -6,7 +6,7 @@ import {Link} from '../component/link.js'; import Modal from '../component/modal.js'; var PublishPage = React.createClass({ - _requiredFields: ['name', 'bid', 'meta_title'], + _requiredFields: ['meta_title', 'name', 'bid'], _updateChannelList: function(channel) { // Calls API to update displayed list of channels. If a channel name is provided, will select @@ -27,19 +27,23 @@ var PublishPage = React.createClass({ submitting: true, }); - var checkFields = this._requiredFields.slice(); + let checkFields = this._requiredFields; if (!this.state.myClaimExists) { - checkFields.push('file'); + checkFields.unshift('file'); } - var missingFieldFound = false; + let missingFieldFound = false; for (let fieldName of checkFields) { - var field = this.refs[fieldName]; - if (field.getValue() === '') { - field.showRequiredError(); - if (!missingFieldFound) { - field.focus(); - missingFieldFound = true; + const field = this.refs[fieldName]; + if (field) { + if (field.getValue() === '' || field.getValue() === false) { + field.showRequiredError(); + if (!missingFieldFound) { + field.focus(); + missingFieldFound = true; + } + } else { + field.clearError(); } } } @@ -281,6 +285,8 @@ var PublishPage = React.createClass({ if (newChannelName.length > 1 && !lbry.nameIsValid(newChannelName.substr(1), false)) { this.refs.newChannelName.showError('LBRY channel names must contain only letters, numbers and dashes.'); return; + } else { + this.refs.newChannelName.clearError() } this.setState({ @@ -351,17 +357,24 @@ var PublishPage = React.createClass({ } else if (this.state.myClaimExists) { return "You have already used this URL. Publishing to it again will update your previous publish." } else if (this.state.topClaimValue) { - return A deposit of at least {this.state.topClaimValue} {this.state.topClaimValue == 1 ? 'credit' : 'credits'} + return A deposit of at least {this.state.topClaimValue} {this.state.topClaimValue == 1 ? 'credit ' : 'credits '} is required to win {this.state.name}. However, you can still get a perminent URL for any amount. } else { return ''; } }, + closeModal: function() { + this.setState({ + modal: null, + }); + }, render: function() { if (this.state.channels === null) { return null; } + const lbcInputHelp = "This LBC remains yours and the deposit can be undone at any time." + return (
@@ -377,10 +390,17 @@ var PublishPage = React.createClass({ helper={this.state.myClaimExists ? "If you don't choose a file, the file from your existing claim will be used." : null}/> { !this.state.hasFile ? '' : +
+
+ +
- +
+
+
+
@@ -390,12 +410,15 @@ var PublishPage = React.createClass({ +
+
-
} +
+ }
@@ -409,27 +432,28 @@ var PublishPage = React.createClass({
- { this.handleFeePrefChange(false) } } checked={!this.state.isFee} /> + { this.handleFeePrefChange(false) } } defaultChecked={!this.state.isFee} /> { this.handleFeePrefChange(true) } } checked={this.state.isFee} /> + onChange={ () => { this.handleFeePrefChange(true) } } defaultChecked={this.state.isFee} /> - + { this.state.isFee ? -
+
If you choose to price this content in dollars, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase.
: '' } + + - @@ -465,7 +489,13 @@ var PublishPage = React.createClass({
{ this.refs.newChannelName = newChannelName }} value={this.state.newChannelName} /> - +
@@ -477,11 +507,11 @@ var PublishPage = React.createClass({

Address

-
Where should this content permanently reside?
+
Where should this content permanently reside? .
- Select a URL for this publish. .
)} /> +
{ this.state.rawName ?
@@ -489,10 +519,11 @@ var PublishPage = React.createClass({ type="number" step="0.01" label="Deposit" + postfix="LBC" onChange={this.handleBidChange} - value={this.state.bid} + value={this.state.bid ? this.state.bid : '1'} placeholder={this.state.nameResolved ? this.state.topClaimValue + 10 : 100} - helper={this.getNameBidHelpText()} /> + helper={lbcInputHelp} />
: '' }
diff --git a/ui/js/page/settings.js b/ui/js/page/settings.js index d6df42cfb..318711d8b 100644 --- a/ui/js/page/settings.js +++ b/ui/js/page/settings.js @@ -3,36 +3,51 @@ import {FormField, FormRow} from '../component/form.js'; import lbry from '../lbry.js'; var SettingsPage = React.createClass({ + _onSettingSaveSuccess: function() { + // This is bad. + // document.dispatchEvent(new CustomEvent('globalNotice', { + // detail: { + // message: "Settings saved", + // }, + // })) + }, + setDaemonSetting: function(name, value) { + lbry.setDaemonSetting(name, value, this._onSettingSaveSuccess) + }, + setClientSetting: function(name, value) { + lbry.setClientSetting(name, value) + this._onSettingSaveSuccess() + }, onRunOnStartChange: function (event) { - lbry.setDaemonSetting('run_on_startup', event.target.checked); + this.setDaemonSetting('run_on_startup', event.target.checked); }, onShareDataChange: function (event) { - lbry.setDaemonSetting('share_debug_info', event.target.checked); + this.setDaemonSetting('share_debug_info', event.target.checked); }, onDownloadDirChange: function(event) { - lbry.setDaemonSetting('download_directory', event.target.value); + this.setDaemonSetting('download_directory', event.target.value); }, onMaxUploadPrefChange: function(isLimited) { if (!isLimited) { - lbry.setDaemonSetting('max_upload', 0.0); + this.setDaemonSetting('max_upload', 0.0); } this.setState({ isMaxUpload: isLimited }); }, onMaxUploadFieldChange: function(event) { - lbry.setDaemonSetting('max_upload', Number(event.target.value)); + this.setDaemonSetting('max_upload', Number(event.target.value)); }, onMaxDownloadPrefChange: function(isLimited) { if (!isLimited) { - lbry.setDaemonSetting('max_download', 0.0); + this.setDaemonSetting('max_download', 0.0); } this.setState({ isMaxDownload: isLimited }); }, onMaxDownloadFieldChange: function(event) { - lbry.setDaemonSetting('max_download', Number(event.target.value)); + this.setDaemonSetting('max_download', Number(event.target.value)); }, getInitialState: function() { return { @@ -57,7 +72,7 @@ var SettingsPage = React.createClass({ lbry.setClientSetting('showNsfw', event.target.checked); }, onShowUnavailableChange: function(event) { - lbry.setClientSetting('showUnavailable', event.target.checked); + }, render: function() { if (!this.state.daemonSettings) { @@ -94,38 +109,60 @@ var SettingsPage = React.createClass({

Bandwidth Limits

-

Max Upload

+
Max Upload
- - { this.state.isMaxUpload ? - : '' - } +
+ + { this.state.isMaxUpload ? + + : '' + + } + { this.state.isMaxUpload ? MB/s : '' } +
-

Max Download

-
Max Download
+ - { /* - */ } +
+ + { this.state.isMaxDownload ? + + : '' + + } + { this.state.isMaxDownload ? MB/s : '' } +
@@ -160,25 +197,4 @@ var SettingsPage = React.createClass({ } }); -/* - -
-

Search

-
-
- Would you like search results to include items that are not currently available for download? -
- -
-
-
-

Share Diagnostic Data

- -
- */ - export default SettingsPage; diff --git a/ui/js/page/wallet.js b/ui/js/page/wallet.js index e8cb8abb0..8540e5469 100644 --- a/ui/js/page/wallet.js +++ b/ui/js/page/wallet.js @@ -152,7 +152,7 @@ var SendToAddressSection = React.createClass({

Send Credits

- +
diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index 608b49007..163de32d4 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -137,7 +137,7 @@ $height-card-small: $spacing-vertical * 15; overflow-y: hidden; white-space: nowrap; padding-left: 20px; - margin-left: -20px; /*hacky way to give space for hover */ + margin-left: -20px; /*hacky way to give space for hover */ } .card-row__header { margin-bottom: $spacing-vertical / 3; diff --git a/ui/scss/component/_file-tile.scss b/ui/scss/component/_file-tile.scss index a3dc30d28..9ff9b2c75 100644 --- a/ui/scss/component/_file-tile.scss +++ b/ui/scss/component/_file-tile.scss @@ -1,16 +1,19 @@ @import "../global"; .file-tile__row { - height: $spacing-vertical * 7; - .credit-amount { float: right; } + //Hack! Remove below! + .card__title-primary { + margin-top: $spacing-vertical * 2/3; + } } .file-tile__thumbnail { max-width: 100%; max-height: $spacing-vertical * 7; + vertical-align: middle; display: block; margin-left: auto; margin-right: auto; diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index 14744326b..c9f00b141 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -8,10 +8,10 @@ $width-input-border: 2px; } .form-row__label-row { - margin-top: $spacing-vertical * 2/3; - margin-bottom: $spacing-vertical * 1/3; + margin-top: $spacing-vertical * 5/6; + margin-bottom: $spacing-vertical * 1/6; line-height: 1; - font-size: 0.9em; + font-size: 0.9 * $font-size; } .form-row__label-row--prefix { float: left; @@ -72,6 +72,13 @@ $width-input-border: 2px; &.form-field__input--error { border-color: $color-error; } + &.form-field__input--inline { + padding-top: 0; + padding-bottom: 0; + border-bottom-width: 1px; + margin-left: 8px; + margin-right: 8px; + } } textarea:focus, @@ -104,6 +111,13 @@ $width-input-border: 2px; width: 330px; } +.form-field__prefix { + margin-right: 4px; +} +.form-field__postfix { + margin-left: 4px; +} + .form-field__input-number { width: 70px; text-align: right; From a5d1695084e269e85042ce6867e2891064bdf447 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 12 Apr 2017 12:59:43 -0400 Subject: [PATCH 30/51] more tweaks and turn off auth --- ui/js/component/auth.js | 20 +++++++++++--------- ui/js/component/form.js | 3 ++- ui/js/page/publish.js | 16 +++++++--------- ui/scss/_gui.scss | 2 +- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/ui/js/component/auth.js b/ui/js/component/auth.js index bf3fb4232..aa3937e63 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth.js @@ -104,20 +104,20 @@ const ConfirmEmailStage = React.createClass({ }); const WelcomeStage = React.createClass({ - onRewardClaim: function() { - console.log('omg'); - }, render: function() { + //

Thank you return (

Welcome to LBRY.

-

LBRY is the first community controlled content marketplace.

-

Since you're new here, we'll toss you some credits.

+

LBRY is kind of like 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. Through blockchain and decentralization, LBRY is controlled by it's users -- that is, you.

+

Here is a reward for reading our weird centaur metaphor:

-

LBC is blah blah blah.

-

And remember, LBRY is a beta and be safe!

+

This reward earned you LBC. LBC is used to watch stuff and to have say in how the network works.

+

But no need to understand it all just yet! Try watching something next.

); } @@ -157,7 +157,7 @@ export const AuthOverlay = React.createClass({ getInitialState: function() { return { - stage: "pending", + stage: null, stageProps: {} }; }, @@ -200,7 +200,9 @@ export const AuthOverlay = React.createClass({ }, render: function() { if (!this.state.stage || lbryio.user && lbryio.user.HasVerifiedEmail) { - return null; + if (this.state.stage != "welcome") { + return null; + } } const StageContent = this._stages[this.state.stage]; return ( diff --git a/ui/js/component/form.js b/ui/js/component/form.js index c24bd7b92..ef8a7169b 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -50,7 +50,8 @@ export let FormField = React.createClass({ if (this.props.type == 'checkbox') { return this.refs.field.checked; } else if (this.props.type == 'file') { - return !!(this.refs.field.files.length && this.refs.field.files[0].path); + return this.refs.field.files.length && this.refs.field.files[0].path ? + this.refs.field.files[0].path : null; } else { return this.refs.field.value; } diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 7ec3ebd0c..f00a30370 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -86,12 +86,10 @@ var PublishPage = React.createClass({ }; if (this.refs.file.getValue() !== '') { - publishArgs.file_path = this.refs.file.getValue(); + publishArgs.file_path = this.refs.file.getValue(); } - console.log(publishArgs); lbry.publish(publishArgs, (message) => { - console.log(message); this.handlePublishStarted(); }, null, (error) => { this.handlePublishError(error); @@ -117,13 +115,13 @@ var PublishPage = React.createClass({ channels: null, rawName: '', name: '', - bid: 0.01, + bid: 1, hasFile: false, feeAmount: '', feeCurrency: 'USD', channel: 'anonymous', newChannelName: '@', - newChannelBid: '', + newChannelBid: 10, nameResolved: false, topClaimValue: 0.0, myClaimValue: 0.0, @@ -392,10 +390,10 @@ var PublishPage = React.createClass({ { !this.state.hasFile ? '' :
- +
- +
@@ -495,7 +493,7 @@ var PublishPage = React.createClass({ type="number" helper={lbcInputHelp} onChange={this.handleNewChannelBidChange} - value={this.state.newChannelBid ? this.state.newChannelBid : '10'} /> + value={this.state.newChannelBid} />
@@ -521,7 +519,7 @@ var PublishPage = React.createClass({ label="Deposit" postfix="LBC" onChange={this.handleBidChange} - value={this.state.bid ? this.state.bid : '1'} + value={this.state.bid} placeholder={this.state.nameResolved ? this.state.topClaimValue + 10 : 100} helper={lbcInputHelp} />
: '' } diff --git a/ui/scss/_gui.scss b/ui/scss/_gui.scss index f1cf53c40..15246c7f6 100644 --- a/ui/scss/_gui.scss +++ b/ui/scss/_gui.scss @@ -174,7 +174,7 @@ p border-radius: 4px; padding: $spacing-vertical; box-shadow: $default-box-shadow; - max-width: 250px; + max-width: 400px; } .modal__header { From b38998dc184314aee5ae6abdbb3629f0308ba064 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 12 Apr 2017 16:23:20 -0400 Subject: [PATCH 31/51] more fixes --- ui/js/component/auth.js | 73 ++++++++++++++++++--------- ui/js/component/link.js | 9 ++-- ui/js/component/modal-page.js | 2 +- ui/js/component/modal.js | 4 +- ui/js/component/snack-bar.js | 1 - ui/js/lbry.js | 15 +++--- ui/js/page/publish.js | 15 ++++++ ui/js/page/settings.js | 25 ++++----- ui/js/rewards.js | 31 ++++++++++-- ui/scss/_gui.scss | 76 ---------------------------- ui/scss/all.scss | 1 + ui/scss/component/_modal-page.scss | 14 +----- ui/scss/component/_modal.scss | 81 ++++++++++++++++++++++++++++++ 13 files changed, 204 insertions(+), 143 deletions(-) create mode 100644 ui/scss/component/_modal.scss diff --git a/ui/js/component/auth.js b/ui/js/component/auth.js index aa3937e63..a42d3cc06 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth.js @@ -104,21 +104,45 @@ const ConfirmEmailStage = React.createClass({ }); const WelcomeStage = React.createClass({ + propTypes: { + endAuth: React.PropTypes.func, + }, + getInitialState: function() { + return { + hasReward: false, + rewardAmount: null, + } + }, + onRewardClaim: function(reward) { + this.setState({ + hasReward: true, + rewardAmount: reward + }) + }, render: function() { - //

Thank you return ( -

-

Welcome to LBRY.

-

LBRY is kind of like 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. Through blockchain and decentralization, LBRY is controlled by it's users -- that is, you.

-

Here is a reward for reading our weird centaur metaphor:

-
- -
-

This reward earned you LBC. LBC is used to watch stuff and to have say in how the network works.

-

But no need to understand it all just yet! Try watching something next.

-
+ !this.state.hasReward ? + +
+

Welcome to LBRY.

+

LBRY is kind of like 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.

+

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

+
+ +
+
+
: + +
+

About Your Reward

+

You earned a reward of 5 LBRY credits, or LBC.

+

This reward will show in your Wallet momentarily, likely while you are reading this message.

+

LBC is used to compensate creators, to publish, and to have say in how the network works.

+

No need to understand it all just yet! Try watching or downloading something next.

+
+
); } }); @@ -152,12 +176,11 @@ export const AuthOverlay = React.createClass({ error: ErrorStage, email: SubmitEmailStage, confirm: ConfirmEmailStage, - welcome: WelcomeStage, + welcome: WelcomeStage }, - getInitialState: function() { return { - stage: null, + stage: "pending", stageProps: {} }; }, @@ -183,7 +206,13 @@ export const AuthOverlay = React.createClass({ } }) } else { - this.endAuth() + lbryio.call('reward', 'list', {}).then(function(userRewards) { + userRewards.filter(function(reward) { + return reward.RewardType == "new_user" && reward.TransactionID; + }).length ? + this.endAuth() : + this.setState({ stage: "welcome" }) + }.bind(this)); } }.bind(this)).catch((err) => { this.setState({ @@ -199,21 +228,17 @@ export const AuthOverlay = React.createClass({ }) }, render: function() { - if (!this.state.stage || lbryio.user && lbryio.user.HasVerifiedEmail) { - if (this.state.stage != "welcome") { + if (!this.state.stage) { return null; - } } const StageContent = this._stages[this.state.stage]; return ( this.state.stage != "welcome" ? - +

LBRY Early Access

: - - - + ); } }); \ No newline at end of file diff --git a/ui/js/component/link.js b/ui/js/component/link.js index 8bcaddd48..88e60bf7e 100644 --- a/ui/js/component/link.js +++ b/ui/js/component/link.js @@ -60,7 +60,8 @@ export let RewardLink = React.createClass({ propTypes: { type: React.PropTypes.string.isRequired, claimed: React.PropTypes.bool, - onRewardClaim: React.PropTypes.func + onRewardClaim: React.PropTypes.func, + onRewardFailure: React.PropTypes.func }, refreshClaimable: function() { switch(this.props.type) { @@ -92,7 +93,6 @@ export let RewardLink = React.createClass({ pending: true }) rewards.claimReward(this.props.type).then(function(reward) { - console.log(reward); this.setState({ pending: false, errorMessage: null @@ -108,6 +108,9 @@ export let RewardLink = React.createClass({ }.bind(this)) }, clearError: function() { + if (this.props.onRewardFailure) { + this.props.onRewardFailure() + } this.setState({ errorMessage: null }) @@ -117,7 +120,7 @@ export let RewardLink = React.createClass({
{this.props.claimed ? Reward claimed. - : } + : } {this.state.errorMessage ? {this.state.errorMessage} diff --git a/ui/js/component/modal-page.js b/ui/js/component/modal-page.js index d0def4b82..12826a81e 100644 --- a/ui/js/component/modal-page.js +++ b/ui/js/component/modal-page.js @@ -6,7 +6,7 @@ export const ModalPage = React.createClass({ return ( + overlayClassName="modal-overlay">
{this.props.children}
diff --git a/ui/js/component/modal.js b/ui/js/component/modal.js index bd534ecea..dbb8ff646 100644 --- a/ui/js/component/modal.js +++ b/ui/js/component/modal.js @@ -6,6 +6,7 @@ import {Link} from './link.js'; export const Modal = React.createClass({ propTypes: { type: React.PropTypes.oneOf(['alert', 'confirm', 'custom']), + overlay: React.PropTypes.bool, onConfirmed: React.PropTypes.func, onAborted: React.PropTypes.func, confirmButtonLabel: React.PropTypes.string, @@ -16,6 +17,7 @@ export const Modal = React.createClass({ getDefaultProps: function() { return { type: 'alert', + overlay: true, confirmButtonLabel: 'OK', abortButtonLabel: 'Cancel', confirmButtonDisabled: false, @@ -26,7 +28,7 @@ export const Modal = React.createClass({ return ( + overlayClassName={[null, undefined, ""].indexOf(this.props.overlayClassName) === -1 ? this.props.overlayClassName : 'modal-overlay'}>
{this.props.children}
diff --git a/ui/js/component/snack-bar.js b/ui/js/component/snack-bar.js index e1ddb01b0..a993c3b75 100644 --- a/ui/js/component/snack-bar.js +++ b/ui/js/component/snack-bar.js @@ -13,7 +13,6 @@ export const SnackBar = React.createClass({ } }, handleSnackReceived: function(event) { - // console.log(event); // if (this._hideTimeout) { // clearTimeout(this._hideTimeout); // } diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 51abc311b..0360453b7 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -204,13 +204,6 @@ lbry.resolveName = function(name, callback) { }); } -lbry.getStream = function(name, callback) { - if (!name) { - throw new Error(`Name required.`); - } - lbry.call('get', { 'name': name }, callback); -}; - lbry.getClaimInfo = function(name, callback) { if (!name) { throw new Error(`Name required.`); @@ -654,6 +647,14 @@ lbry.claim_list_mine = function(params={}) { }); } +// lbry.get = function(params={}) { +// return function(params={}) { +// return new Promise((resolve, reject) => { +// jsonrpc.call(lbry.daemonConnectionString, "get", [params], resolve, reject, reject); +// }); +// }; +// } + lbry = new Proxy(lbry, { get: function(target, name) { if (name in target) { diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index f00a30370..2709f3a8e 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -3,11 +3,25 @@ import lbry from '../lbry.js'; import uri from '../uri.js'; import {FormField, FormRow} from '../component/form.js'; import {Link} from '../component/link.js'; +import rewards from '../rewards.js'; import Modal from '../component/modal.js'; var PublishPage = React.createClass({ _requiredFields: ['meta_title', 'name', 'bid'], + _requestPublishReward: function() { + lbryio.call('reward', 'list', {}).then(function(userRewards) { + //already rewarded + if (userRewards.filter(function (reward) { + return reward.RewardType == rewards.TYPE_FIRST_PUBLISH && reward.TransactionID; + }).length) { + return; + } + else { + rewards.claimReward(rewards.TYPE_FIRST_PUBLISH) + } + }); + }, _updateChannelList: function(channel) { // 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) @@ -334,6 +348,7 @@ var PublishPage = React.createClass({ }, componentWillMount: function() { this._updateChannelList(); + this._requestPublishReward(); }, componentDidMount: function() { document.title = "Publish"; diff --git a/ui/js/page/settings.js b/ui/js/page/settings.js index 318711d8b..d05be08fe 100644 --- a/ui/js/page/settings.js +++ b/ui/js/page/settings.js @@ -78,20 +78,21 @@ var SettingsPage = React.createClass({ if (!this.state.daemonSettings) { return null; } - +/* +
+
+

Run on Startup

+
+
+ +
+
+ */ return (
-
-
-

Run on Startup

-
-
- -
-

Download Directory

diff --git a/ui/js/rewards.js b/ui/js/rewards.js index 29f43058c..a42ed1637 100644 --- a/ui/js/rewards.js +++ b/ui/js/rewards.js @@ -3,23 +3,44 @@ import lbryio from './lbryio.js'; function rewardMessage(type, amount) { return { - new_developer: "Your reward has been confirmed for registering as a new developer.", + new_developer: "You received ${amount} for registering as a new developer.", new_user: `You received ${amount} LBC new user reward.`, - confirm_email: "Your reward has been confirmed for verifying your email address.", - first_publish: "Your reward has been confirmed for making your first publication.", + confirm_email: "You received ${amount} LBC for verifying your email address.", + first_channel: "You received ${amount} LBC for creating a publisher identity.", + first_purchase: "You received ${amount} LBC for making your first purchase.", + first_publish: "You received ${amount} LBC for making your first publication.", }[type]; } const rewards = {}; -rewards.claimReward = function(type) { +rewards.TYPE_NEW_DEVELOPER = "new_developer", + rewards.TYPE_NEW_USER = "new_user", + rewards.TYPE_CONFIRM_EMAIL = "confirm_email", + rewards.TYPE_FIRST_CHANNEL = "first_channel", + rewards.TYPE_FIRST_PURCHASE = "first_purchase", + rewards.TYPE_FIRST_PUBLISH = "first_publish"; + +rewards.claimReward = function (type) { return new Promise((resolve, reject) => { - console.log('top of promise body') lbry.get_new_address().then((address) => { const params = { reward_type: type, wallet_address: address, }; + switch (type) { + case 'first_channel': + //params.transaction_id = RelevantTransactionID; + break; + + case 'first_purchase': + //params.transaction_id = RelevantTransactionID; + break; + + case 'first_channel': + //params.transaction_id = RelevantTransactionID; + break; + } lbryio.call('reward', 'new', params, 'post').then(({RewardAmount}) => { const message = rewardMessage(type, RewardAmount), diff --git a/ui/scss/_gui.scss b/ui/scss/_gui.scss index 15246c7f6..da32d3ac5 100644 --- a/ui/scss/_gui.scss +++ b/ui/scss/_gui.scss @@ -147,79 +147,3 @@ p font-size: 0.85em; color: $color-help; } - -.modal-overlay { - position: fixed; - display: flex; - justify-content: center; - align-items: center; - - top: 0px; - left: 0px; - right: 0px; - bottom: 0px; - background-color: rgba(255, 255, 255, 0.74902); - z-index: 9999; -} - -.modal { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - border: 1px solid rgb(204, 204, 204); - background: rgb(255, 255, 255); - overflow: auto; - border-radius: 4px; - padding: $spacing-vertical; - box-shadow: $default-box-shadow; - max-width: 400px; -} - -.modal__header { - margin-bottom: 5px; - text-align: center; -} - -.modal__buttons { - display: flex; - flex-direction: row; - justify-content: center; - margin-top: 15px; -} - -.modal__button { - margin: 0px 6px; -} - -.error-modal-overlay { - background: rgba(#000, .88); -} - -.error-modal__content { - display: flex; - padding: 0px 8px 10px 10px; -} - -.error-modal__warning-symbol { - margin-top: 6px; - margin-right: 7px; -} - -.download-started-modal__file-path { - word-break: break-all; -} - -.error-modal { - max-width: none; - width: 400px; -} -.error-modal__error-list { /*shitty hack/temp fix for long errors making modals unusable*/ - border: 1px solid #eee; - padding: 8px; - list-style: none; - max-height: 400px; - max-width: 400px; - overflow-y: hidden; -} diff --git a/ui/scss/all.scss b/ui/scss/all.scss index 18a9ec720..dcb81cb98 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -15,6 +15,7 @@ @import "component/_load-screen.scss"; @import "component/_channel-indicator.scss"; @import "component/_notice.scss"; +@import "component/_modal.scss"; @import "component/_modal-page.scss"; @import "component/_snack-bar.scss"; @import "page/_developer.scss"; diff --git a/ui/scss/component/_modal-page.scss b/ui/scss/component/_modal-page.scss index 2b86c5cad..d9bd0d8d5 100644 --- a/ui/scss/component/_modal-page.scss +++ b/ui/scss/component/_modal-page.scss @@ -1,16 +1,4 @@ -.modal-page-overlay { - position: fixed; - display: flex; - justify-content: center; - align-items: center; - - top: 0px; - left: 0px; - right: 0px; - bottom: 0px; - background-color: rgba(255, 255, 255, 0.74902); - z-index: 9999; -} +@import "../global"; .modal-page { position: fixed; diff --git a/ui/scss/component/_modal.scss b/ui/scss/component/_modal.scss new file mode 100644 index 000000000..13284c7ff --- /dev/null +++ b/ui/scss/component/_modal.scss @@ -0,0 +1,81 @@ +@import "../global"; + +.modal-overlay { + position: fixed; + display: flex; + justify-content: center; + align-items: center; + + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + background-color: rgba(255, 255, 255, 0.74902); + z-index: 9999; +} + +.modal-overlay--clear { + background-color: transparent; +} + +.modal { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + border: 1px solid rgb(204, 204, 204); + background: rgb(255, 255, 255); + overflow: auto; + border-radius: 4px; + padding: $spacing-vertical; + box-shadow: $default-box-shadow; + max-width: 400px; +} + +.modal__header { + margin-bottom: 5px; + text-align: center; +} + +.modal__buttons { + display: flex; + flex-direction: row; + justify-content: center; + margin-top: 15px; +} + +.modal__button { + margin: 0px 6px; +} + +.error-modal-overlay { + background: rgba(#000, .88); +} + +.error-modal__content { + display: flex; + padding: 0px 8px 10px 10px; +} + +.error-modal__warning-symbol { + margin-top: 6px; + margin-right: 7px; +} + +.download-started-modal__file-path { + word-break: break-all; +} + +.error-modal { + max-width: none; + width: 400px; +} +.error-modal__error-list { /*shitty hack/temp fix for long errors making modals unusable*/ + border: 1px solid #eee; + padding: 8px; + list-style: none; + max-height: 400px; + max-width: 400px; + overflow-y: hidden; +} \ No newline at end of file From dce87e9079137067ee078bfd7949c38d85be29bf Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 12 Apr 2017 01:02:29 -0400 Subject: [PATCH 32/51] Switch name check to URI check in lbry.getCostInfo() --- ui/js/lbry.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 0360453b7..e87dee4b0 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -243,6 +243,10 @@ lbry.getCostInfo = function(lbryUri, callback, errorCallback) { * - includes_data: Boolean; indicates whether or not the data fee info * from Lighthouse is included. */ + if (!lbryUri) { + throw new Error(`URI required.`); + } + function getCostWithData(name, size, callback, errorCallback) { lbry.stream_cost_estimate({name, size}).then((cost) => { callback({ From d3258d9de6e846189b21b9b3b0efc0e0bc060208 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 12 Apr 2017 13:08:23 -0400 Subject: [PATCH 33/51] Convert lbry.getCostInfo() to use URIs --- ui/js/lbry.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/js/lbry.js b/ui/js/lbry.js index e87dee4b0..941c24244 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -247,8 +247,8 @@ lbry.getCostInfo = function(lbryUri, callback, errorCallback) { throw new Error(`URI required.`); } - function getCostWithData(name, size, callback, errorCallback) { - lbry.stream_cost_estimate({name, size}).then((cost) => { + function getCostWithData(lbryUri, size, callback, errorCallback) { + lbry.stream_cost_estimate({uri: lbryUri, size}).then((cost) => { callback({ cost: cost, includesData: true, @@ -256,8 +256,8 @@ lbry.getCostInfo = function(lbryUri, callback, errorCallback) { }, errorCallback); } - function getCostNoData(name, callback, errorCallback) { - lbry.stream_cost_estimate({name}).then((cost) => { + function getCostNoData(lbryUri, callback, errorCallback) { + lbry.stream_cost_estimate({uri: lbryUri}).then((cost) => { callback({ cost: cost, includesData: false, From b9f0ed2f5b292d9e7d101a9cf2e0aea55a5754ad Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 12 Apr 2017 14:14:19 -0400 Subject: [PATCH 34/51] Refactor lbry.getCostInfo() --- ui/js/lbry.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 941c24244..4c439db79 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -247,20 +247,11 @@ lbry.getCostInfo = function(lbryUri, callback, errorCallback) { throw new Error(`URI required.`); } - function getCostWithData(lbryUri, size, callback, errorCallback) { - lbry.stream_cost_estimate({uri: lbryUri, size}).then((cost) => { + function getCost(lbryUri, size, callback, errorCallback) { + lbry.stream_cost_estimate({uri: lbryUri, ... size !== null ? {size} : {}}).then((cost) => { callback({ cost: cost, - includesData: true, - }); - }, errorCallback); - } - - function getCostNoData(lbryUri, callback, errorCallback) { - lbry.stream_cost_estimate({uri: lbryUri}).then((cost) => { - callback({ - cost: cost, - includesData: false, + includesData: size !== null, }); }, errorCallback); } @@ -269,9 +260,9 @@ lbry.getCostInfo = function(lbryUri, callback, errorCallback) { const name = uriObj.path || uriObj.name; lighthouse.get_size_for_name(name).then((size) => { - getCostWithData(name, size, callback, errorCallback); + getCost(name, size, callback, errorCallback); }, () => { - getCostNoData(name, callback, errorCallback); + getCost(name, null, callback, errorCallback); }); } From dabedf38a100af2cd906a3d899c2700244a002c6 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 12 Apr 2017 19:24:04 -0400 Subject: [PATCH 35/51] Update and refactor pending publish caching system for channels --- ui/js/lbry.js | 107 +++++++++++++++------------------------- ui/js/page/file-list.js | 2 +- 2 files changed, 41 insertions(+), 68 deletions(-) diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 4c439db79..6397ca9f5 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -10,24 +10,32 @@ const menu = remote.require('./menu/main-menu'); * Records a publish attempt in local storage. Returns a dictionary with all the data needed to * needed to make a dummy claim or file info object. */ -function savePendingPublish(name) { +function savePendingPublish({name, channel_name}) { + const lbryUri = uri.buildLbryUri({name, channel_name}, false); const pendingPublishes = getLocal('pendingPublishes') || []; const newPendingPublish = { - claim_id: 'pending_claim_' + name, - txid: 'pending_' + name, + name, channel_name, + claim_id: 'pending_claim_' + lbryUri, + txid: 'pending_' + lbryUri, nout: 0, - outpoint: 'pending_' + name + ':0', - name: name, + outpoint: 'pending_' + lbryUri + ':0', time: Date.now(), }; setLocal('pendingPublishes', [...pendingPublishes, newPendingPublish]); return newPendingPublish; } -function removePendingPublish({name, outpoint}) { - setLocal('pendingPublishes', getPendingPublishes().filter( - (pub) => pub.name != name && pub.outpoint != outpoint - )); + +/** + * If there is a pending publish with the given name or outpoint, remove it. + * A channel name may also be provided along with name. + */ +function removePendingPublishIfNeeded({name, channel_name, outpoint}) { + function pubMatches(pub) { + return pub.outpoint === outpoint || (pub.name === name && (!channel_name || pub.channel_name === channel_name)); + } + + setLocal('pendingPublishes', getPendingPublishes().filter(pub => !pubMatches(pub))); } /** @@ -36,59 +44,28 @@ function removePendingPublish({name, outpoint}) { */ function getPendingPublishes() { const pendingPublishes = getLocal('pendingPublishes') || []; - - const newPendingPublishes = []; - for (let pendingPublish of pendingPublishes) { - if (Date.now() - pendingPublish.time <= lbry.pendingPublishTimeout) { - newPendingPublishes.push(pendingPublish); - } - } + const newPendingPublishes = pendingPublishes.filter(pub => Date.now() - pub.time <= lbry.pendingPublishTimeout); setLocal('pendingPublishes', newPendingPublishes); - return newPendingPublishes + return newPendingPublishes; } /** - * Gets a pending publish attempt by its name or (fake) outpoint. If none is found (or one is found - * but it has timed out), returns null. + * Gets a pending publish attempt by its name or (fake) outpoint. A channel name can also be + * provided along withe the name. If no pending publish is found, returns null. */ -function getPendingPublish({name, outpoint}) { +function getPendingPublish({name, channel_name, outpoint}) { const pendingPublishes = getPendingPublishes(); - const pendingPublishIndex = pendingPublishes.findIndex( - ({name: itemName, outpoint: itemOutpoint}) => itemName == name || itemOutpoint == outpoint - ); - const pendingPublish = pendingPublishes[pendingPublishIndex]; - - if (pendingPublishIndex == -1) { - return null; - } else if (Date.now() - pendingPublish.time > lbry.pendingPublishTimeout) { - // Pending publish timed out, so remove it from the stored list and don't match - - const newPendingPublishes = pendingPublishes.slice(); - newPendingPublishes.splice(pendingPublishIndex, 1); - setLocal('pendingPublishes', newPendingPublishes); - return null; - } else { - return pendingPublish; - } + return pendingPublishes.find( + pub => pub.outpoint === outpoint || (pub.name === name && (!channel_name || pub.channel_name === channel_name)) + ) || null; } -function pendingPublishToDummyClaim({name, outpoint, claim_id, txid, nout}) { - return { - name: name, - outpoint: outpoint, - claim_id: claim_id, - txid: txid, - nout: nout, - }; +function pendingPublishToDummyClaim({channel_name, name, outpoint, claim_id, txid, nout}) { + return {name, outpoint, claim_id, txid, nout, ... channel_name ? {channel_name} : {}}; } function pendingPublishToDummyFileInfo({name, outpoint, claim_id}) { - return { - name: name, - outpoint: outpoint, - claim_id: claim_id, - metadata: "Attempting publication", - }; + return {name, outpoint, claim_id, metadata: {stream: {metadata: 'Attempting publication'}}}; } @@ -330,12 +307,14 @@ lbry.publish = function(params, fileListedCallback, publishedCallback, errorCall returnedPending = true; if (publishedCallback) { - savePendingPublish(params.name); + const {name, channel_name} = params; + savePendingPublish({name, ... channel_name ? {channel_name} : {}}); publishedCallback(true); } if (fileListedCallback) { - savePendingPublish(params.name); + const {name, channel_name} = params; + savePendingPublish({name, ... channel_name ? {channel_name} : {}}); fileListedCallback(true); } }, 2000); @@ -599,14 +578,14 @@ lbry.showMenuIfNeeded = function() { */ lbry.file_list = function(params={}) { return new Promise((resolve, reject) => { - const {name, outpoint} = params; + const {name, channel_name, outpoint} = params; /** * If we're searching by outpoint, check first to see if there's a matching pending publish. * Pending publishes use their own faux outpoints that are always unique, so we don't need * to check if there's a real file. */ - if (outpoint !== undefined) { + if (outpoint) { const pendingPublish = getPendingPublish({outpoint}); if (pendingPublish) { resolve([pendingPublishToDummyFileInfo(pendingPublish)]); @@ -615,14 +594,8 @@ lbry.file_list = function(params={}) { } lbry.call('file_list', params, (fileInfos) => { - // Remove any pending publications that are now listed in the file manager + removePendingPublishIfNeeded({name, channel_name, outpoint}); - const pendingPublishes = getPendingPublishes(); - for (let {name: itemName} of fileInfos) { - if (pendingPublishes.find(() => name == itemName)) { - removePendingPublish({name: name}); - } - } const dummyFileInfos = getPendingPublishes().map(pendingPublishToDummyFileInfo); resolve([...fileInfos, ...dummyFileInfos]); }, reject, reject); @@ -632,13 +605,13 @@ lbry.file_list = function(params={}) { lbry.claim_list_mine = function(params={}) { return new Promise((resolve, reject) => { lbry.call('claim_list_mine', params, (claims) => { - // Filter out pending publishes when the name is already in the file manager - const dummyClaims = getPendingPublishes().filter( - (pub) => !claims.find(({name}) => name == pub.name) - ).map(pendingPublishToDummyClaim); + for (let {name, channel_name, txid, nout} of claims) { + removePendingPublishIfNeeded({name, channel_name, outpoint: txid + ':' + nout}); + } + const dummyClaims = getPendingPublishes().map(pendingPublishToDummyClaim); resolve([...claims, ...dummyClaims]); - }, reject, reject); + }, reject, reject) }); } diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index 518bb85d6..4a3f56f50 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -162,7 +162,7 @@ export let FileList = React.createClass({ const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos); for (let {outpoint, name, channel_name, metadata: {stream: {metadata}}, mime_type, claim_id, has_signature, signature_is_valid} of fileInfosSorted) { - if (!metadata || seenUris[name]) { + if (!metadata || seenUris[name] || channel_name === null) { continue; } From c8c97e97ca5825aadec9f83cfbbf7b6a4734be9b Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 13 Apr 2017 14:52:26 -0400 Subject: [PATCH 36/51] update --- lbry | 2 +- lbryum | 2 +- ui/js/app.js | 2 +- ui/js/component/auth.js | 8 +- ui/js/component/common.js | 74 ++++++++++++++- ui/js/component/file-actions.js | 30 +++++- ui/js/component/file-tile.js | 56 ++--------- ui/js/lbry.js | 71 ++++++++------ ui/js/lighthouse.js | 8 +- ui/js/page/publish.js | 6 +- ui/js/page/show.js | 163 ++++++++++++++------------------ ui/js/page/watch.js | 99 +++++++++++++++++++ ui/js/rewards.js | 71 +++++++++----- ui/js/utils.js | 4 +- ui/scss/_canvas.scss | 10 ++ ui/scss/_global.scss | 2 + ui/scss/all.scss | 2 + ui/scss/component/_card.scss | 7 +- ui/scss/component/_video.scss | 12 +++ ui/scss/page/_show.scss | 6 ++ 20 files changed, 410 insertions(+), 225 deletions(-) create mode 100644 ui/scss/component/_video.scss create mode 100644 ui/scss/page/_show.scss 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 From e3222c853aecb41f77e1a5bf4aecef9aa3e044c4 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 13 Apr 2017 14:57:12 -0400 Subject: [PATCH 37/51] fix merge --- ui/js/lbry.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/ui/js/lbry.js b/ui/js/lbry.js index ef6f6a67c..e7ca23c23 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -198,24 +198,26 @@ lbry.getPeersForBlobHash = function(blobHash, callback) { }); } +/** + * Takes a LBRY URI; will first try and calculate a total cost using + * Lighthouse. If Lighthouse can't be reached, it just retrives the + * key fee. + * + * Returns an object with members: + * - cost: Number; the calculated cost of the name + * - includes_data: Boolean; indicates whether or not the data fee info + * from Lighthouse is included. + */ 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 - * key fee. - * - * Returns an object with members: - * - cost: Number; the calculated cost of the name - * - includes_data: Boolean; indicates whether or not the data fee info - * from Lighthouse is included. - */ - if (!lbryUri) { - throw new Error(`URI required.`); - } return new Promise((resolve, reject) => { + + if (!lbryUri) { + reject(new Error(`URI required.`)); + } + function getCost(lbryUri, size) { lbry.stream_cost_estimate({uri: lbryUri, ... size !== null ? {size} : {}}).then((cost) => { - callback({ + resolve({ cost: cost, includesData: size !== null, }); From 736d769fa69e0d2382f6a975dbe2aa8c4123c583 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 13 Apr 2017 15:37:41 -0400 Subject: [PATCH 38/51] minor fixes, cost cache --- ui/js/component/common.js | 2 +- ui/js/lbry.js | 63 +++++++++++++++++++++++---------------- ui/js/lighthouse.js | 7 +++-- ui/js/page/file-list.js | 4 +-- ui/js/page/show.js | 5 ++-- 5 files changed, 48 insertions(+), 33 deletions(-) diff --git a/ui/js/component/common.js b/ui/js/component/common.js index c4e1324f1..78bb6522e 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -121,7 +121,7 @@ export let FilePrice = React.createClass({ render: function() { if (this.state.cost === null && this.props.metadata) { if (!this.props.metadata.fee) { - return free; + return free*; } else { if (this.props.metadata.fee.currency === "LBC") { return diff --git a/ui/js/lbry.js b/ui/js/lbry.js index e7ca23c23..2b6330656 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -208,35 +208,48 @@ lbry.getPeersForBlobHash = function(blobHash, callback) { * - includes_data: Boolean; indicates whether or not the data fee info * from Lighthouse is included. */ +lbry.costPromiseCache = {} lbry.getCostInfo = function(lbryUri) { - return new Promise((resolve, reject) => { + if (lbry.costPromiseCache[lbryUri] === undefined) { + const COST_INFO_CACHE_KEY = 'cost_info_cache'; + lbry.costPromiseCache[lbryUri] = new Promise((resolve, reject) => { + let costInfoCache = getSession(COST_INFO_CACHE_KEY, {}) - if (!lbryUri) { - reject(new Error(`URI required.`)); - } - - function getCost(lbryUri, size) { - lbry.stream_cost_estimate({uri: lbryUri, ... size !== null ? {size} : {}}).then((cost) => { - resolve({ - cost: cost, - includesData: size !== null, - }); - }, reject); - } - - const uriObj = uri.parseLbryUri(lbryUri); - const name = uriObj.path || uriObj.name; - - lighthouse.get_size_for_name(name).then((size) => { - if (size) { - getCost(name, size); - } else { - getCost(name, null); + if (!lbryUri) { + reject(new Error(`URI required.`)); } - }, () => { - getCost(name, null); + + if (costInfoCache[lbryUri] && costInfoCache[lbryUri].cost) { + return resolve(costInfoCache[lbryUri]) + } + + function getCost(lbryUri, size) { + lbry.stream_cost_estimate({uri: lbryUri, ... size !== null ? {size} : {}}).then((cost) => { + costInfoCache[lbryUri] = { + cost: cost, + includesData: size !== null, + }; + setSession(COST_INFO_CACHE_KEY, costInfoCache); + resolve(costInfoCache[lbryUri]); + }, reject); + } + + const uriObj = uri.parseLbryUri(lbryUri); + const name = uriObj.path || uriObj.name; + + lighthouse.get_size_for_name(name).then((size) => { + if (size) { + getCost(name, size); + } + else { + getCost(name, null); + } + }, () => { + getCost(name, null); + }); }); - }) + } + return lbry.costPromiseCache[lbryUri]; } lbry.getMyClaims = function(callback) { diff --git a/ui/js/lighthouse.js b/ui/js/lighthouse.js index 41d2d996c..faa5b5b67 100644 --- a/ui/js/lighthouse.js +++ b/ui/js/lighthouse.js @@ -1,8 +1,8 @@ import lbry from './lbry.js'; import jsonrpc from './jsonrpc.js'; -const queryTimeout = 5000; -const maxQueryTries = 5; +const queryTimeout = 3000; +const maxQueryTries = 2; const defaultServers = [ 'http://lighthouse4.lbry.io:50005', 'http://lighthouse5.lbry.io:50005', @@ -20,8 +20,9 @@ function getServers() { } function call(method, params, callback, errorCallback) { - if (connectTryNum > maxQueryTries) { + if (connectTryNum >= maxQueryTries) { errorCallback(new Error(`Could not connect to Lighthouse server. Last server attempted: ${server}`)); + return; } /** diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index 4a3f56f50..fdb6c7649 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -162,12 +162,12 @@ export let FileList = React.createClass({ const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos); for (let {outpoint, name, channel_name, metadata: {stream: {metadata}}, mime_type, claim_id, has_signature, signature_is_valid} of fileInfosSorted) { - if (!metadata || seenUris[name] || channel_name === null) { + if (!metadata || seenUris[name]) { continue; } let fileUri; - if (channel_name === undefined) { + if (!channel_name) { fileUri = uri.buildLbryUri({name}); } else { fileUri = uri.buildLbryUri({name: channel_name, path: name}); diff --git a/ui/js/page/show.js b/ui/js/page/show.js index eb9f02e46..f3b361976 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -91,6 +91,7 @@ let ShowPage = React.createClass({ metadata = this.state.uriLookupComplete ? this.state.metadata : null, title = this.state.uriLookupComplete ? metadata.title : this._uri; + console.log(metadata); return (
@@ -101,7 +102,7 @@ let ShowPage = React.createClass({
- +

{title}

{ this.state.uriLookupComplete ?
@@ -109,7 +110,7 @@ let ShowPage = React.createClass({
- +
: '' }
From 83f43ab54f00b4bfeea47a72fd7aae80bdcac777 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 13 Apr 2017 16:35:40 -0400 Subject: [PATCH 39/51] More improvements for better compatibility with new claim format - Update Pending Files cache to match format more closely - Except null instead of string for pending publishes --- ui/js/component/file-tile.js | 8 ++++---- ui/js/lbry.js | 2 +- ui/js/page/file-list.js | 14 +++++++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/ui/js/component/file-tile.js b/ui/js/component/file-tile.js index f9522f1c6..172dea636 100644 --- a/ui/js/component/file-tile.js +++ b/ui/js/component/file-tile.js @@ -13,7 +13,7 @@ export let FileTileStream = React.createClass({ propTypes: { uri: React.PropTypes.string, - metadata: React.PropTypes.object.isRequired, + metadata: React.PropTypes.object, contentType: React.PropTypes.string.isRequired, outpoint: React.PropTypes.string, hasSignature: React.PropTypes.bool, @@ -75,14 +75,14 @@ export let FileTileStream = React.createClass({ const lbryUri = uri.normalizeLbryUri(this.props.uri); const metadata = this.props.metadata; - const isConfirmed = typeof metadata == 'object'; + const isConfirmed = !!metadata; const title = isConfirmed ? metadata.title : lbryUri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; return (
- +
{ !this.props.hidePrice @@ -188,7 +188,7 @@ export let FileCardStream = React.createClass({ const lbryUri = uri.normalizeLbryUri(this.props.uri); const metadata = this.props.metadata; - const isConfirmed = typeof metadata == 'object'; + const isConfirmed = !!metadata; const title = isConfirmed ? metadata.title : lbryUri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; const primaryUrl = '?show=' + lbryUri; diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 2b6330656..b09aabe1f 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -65,7 +65,7 @@ function pendingPublishToDummyClaim({channel_name, name, outpoint, claim_id, txi } function pendingPublishToDummyFileInfo({name, outpoint, claim_id}) { - return {name, outpoint, claim_id, metadata: {stream: {metadata: 'Attempting publication'}}}; + return {name, outpoint, claim_id, null}; } diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index fdb6c7649..3c337565f 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -161,11 +161,19 @@ export let FileList = React.createClass({ seenUris = {}; const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos); - for (let {outpoint, name, channel_name, metadata: {stream: {metadata}}, mime_type, claim_id, has_signature, signature_is_valid} of fileInfosSorted) { - if (!metadata || seenUris[name]) { + for (let {outpoint, name, channel_name, metadata, mime_type, claim_id, has_signature, signature_is_valid} of fileInfosSorted) { + if (seenUris[name] || !claim_id) { continue; } + let streamMetadata; + if (metadata) { + streamMetadata = metadata.stream.metadata; + } else { + streamMetadata = null; + } + + let fileUri; if (!channel_name) { fileUri = uri.buildLbryUri({name}); @@ -174,7 +182,7 @@ export let FileList = React.createClass({ } seenUris[name] = true; content.push(); } From 62db745c3844d32530264fa047f18ce59c8f8954 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 13 Apr 2017 16:40:29 -0400 Subject: [PATCH 40/51] Publish: convert nsfw field to submit a boolean --- ui/js/page/publish.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 524201b97..90dbe4504 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -81,14 +81,16 @@ var PublishPage = React.createClass({ var metadata = {}; } - for (let metaField of ['title', 'description', 'thumbnail', 'license', 'license_url', 'language', 'nsfw']) { + for (let metaField of ['title', 'description', 'thumbnail', 'license', 'license_url', 'language']) { var value = this.refs['meta_' + metaField].getValue(); if (value !== '') { metadata[metaField] = value; } } - var licenseUrl = this.refs.meta_license_url.getValue(); + metadata.nsfw = Boolean(parseInt(!!this.refs.meta_nsfw.getValue())); + + const licenseUrl = this.refs.meta_license_url.getValue(); if (licenseUrl) { metadata.license_url = licenseUrl; } @@ -428,7 +430,7 @@ var PublishPage = React.createClass({
- + {/* */} From 3b29be467ba42de1e10e739b6fc697802043dc3b Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 13 Apr 2017 16:50:24 -0400 Subject: [PATCH 41/51] turn off things --- ui/js/main.js | 2 +- ui/js/page/publish.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/js/main.js b/ui/js/main.js index 544ebb0a2..9d11107d0 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -30,7 +30,7 @@ let init = function() { function onDaemonReady() { window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again - ReactDOM.render(
, canvas) + ReactDOM.render(
, canvas) } if (window.sessionStorage.getItem('loaded') == 'y') { diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 90dbe4504..9267550c9 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -19,7 +19,7 @@ var PublishPage = React.createClass({ return; } else { - rewards.claimReward(rewards.TYPE_FIRST_PUBLISH) + // rewards.claimReward(rewards.TYPE_FIRST_PUBLISH) } }); }, @@ -27,7 +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) + // rewards.claimReward(rewards.TYPE_FIRST_CHANNEL) this.setState({ channels: channels, ... channel ? {channel} : {} From 280f98902cb4ca2ee8efcd6030562de6f095bd7f Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 13 Apr 2017 18:32:03 -0400 Subject: [PATCH 42/51] closer to real watch --- ui/js/app.js | 2 - ui/js/component/file-actions.js | 56 ----------- ui/js/main.js | 1 + ui/js/page/publish.js | 4 +- ui/js/page/show.js | 7 +- ui/js/page/watch.js | 162 ++++++++------------------------ ui/scss/component/_video.scss | 53 ++++++++++- ui/scss/page/_show.scss | 3 + ui/scss/page/_watch.scss | 3 - 9 files changed, 93 insertions(+), 198 deletions(-) diff --git a/ui/js/app.js b/ui/js/app.js index c8f585fb7..9cfb43137 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -256,8 +256,6 @@ var App = React.createClass({ return ; case 'help': return ; - case 'watch': - return ; case 'report': return ; case 'downloaded': diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index 9b81fa28c..1c4c1650a 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -9,59 +9,6 @@ import {DropDownMenu, DropDownMenuItem} from './menu.js'; const {shell} = require('electron'); -let WatchLink = React.createClass({ - propTypes: { - uri: React.PropTypes.string, - downloadStarted: React.PropTypes.bool, - }, - startVideo: function() { - window.location = '?watch=' + this.props.uri; - }, - handleClick: function() { - this.setState({ - loading: true, - }); - - if (this.props.downloadStarted) { - this.startVideo(); - } else { - lbry.getCostInfo(this.props.uri).then(({cost}) => { - lbry.getBalance((balance) => { - if (cost > balance) { - this.setState({ - modal: 'notEnoughCredits', - loading: false, - }); - } else { - this.startVideo(); - } - }); - }); - } - }, - getInitialState: function() { - return { - modal: null, - loading: false, - }; - }, - closeModal: function() { - this.setState({ - modal: null, - }); - }, - render: function() { - return ( -
- - - You don't have enough LBRY credits to pay for this stream. - -
- ); - } -}); - let FileActionsRow = React.createClass({ _isMounted: false, _fileInfoSubscribeId: null, @@ -213,9 +160,6 @@ let FileActionsRow = React.createClass({ return (
- {this.props.contentType && this.props.contentType.startsWith('video/') - ? - : null} {this.state.fileInfo !== null || this.state.fileInfo.isMine ? linkBlock : null} diff --git a/ui/js/main.js b/ui/js/main.js index 9d11107d0..d31b4c7d5 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -30,6 +30,7 @@ let init = function() { function onDaemonReady() { window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again + // ReactDOM.render(
, canvas) } diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 9267550c9..90dbe4504 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -19,7 +19,7 @@ var PublishPage = React.createClass({ return; } else { - // rewards.claimReward(rewards.TYPE_FIRST_PUBLISH) + rewards.claimReward(rewards.TYPE_FIRST_PUBLISH) } }); }, @@ -27,7 +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) + rewards.claimReward(rewards.TYPE_FIRST_CHANNEL) this.setState({ channels: channels, ... channel ? {channel} : {} diff --git a/ui/js/page/show.js b/ui/js/page/show.js index f3b361976..c22dfdeee 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -62,7 +62,6 @@ let ShowPage = React.createClass({ document.title = this._uri; 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, @@ -86,17 +85,15 @@ let ShowPage = React.createClass({ } //
- const metadata = this.state.uriLookupComplete ? this.state.metadata : null, title = this.state.uriLookupComplete ? metadata.title : this._uri; - console.log(metadata); return (
- { this.props.contentType && this.props.contentType.startsWith('video/') ? -
diff --git a/ui/js/page/watch.js b/ui/js/page/watch.js index 45341ded1..bd80c30b2 100644 --- a/ui/js/page/watch.js +++ b/ui/js/page/watch.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Icon} from '../component/common.js'; +import {Icon, Thumbnail} from '../component/common.js'; import {Link} from '../component/link.js'; import lbry from '../lbry.js'; import LoadScreen from '../component/load_screen.js' @@ -20,16 +20,41 @@ export let Video = React.createClass({ return { downloadStarted: false, readyToPlay: false, - loadStatusMessage: "Requesting stream", + isPlaying: false, + isPurchased: false, + loadStatusMessage: "Requesting stream... it may sit here for like 15-20 seconds in a really awkward way... we're working on it", mimeType: null, controlsShown: false, }; }, - componentDidMount: function() { + start: function() { + // lbry.getCostInfo(this.props.uri).then(({cost}) => { + // lbry.getBalance((balance) => { + // if (cost > balance) { + // this.setState({ + // modal: 'notEnoughCredits', + // loading: false, + // }); + // } else { + // this.startVideo(); + // } + // }); + // // }); + // + // You don't have enough LBRY credits to pay for this stream. + // lbry.get({uri: this.props.uri}).then((fileInfo) => { this._outpoint = fileInfo.outpoint; this.updateLoadStatus(); }); + this.setState({ + isPlaying: true + }) + }, + componentDidMount: function() { + if (this.props.autoplay) { + this.start() + } }, handleMouseMove: function() { if (this._controlsTimeout) { @@ -81,7 +106,6 @@ export let Video = React.createClass({ readyToPlay: true, mimeType: status.mime_type, }) - return const mediaFile = { createReadStream: function (opts) { // Return a readable stream that provides the bytes @@ -98,128 +122,16 @@ export let Video = React.createClass({ }, 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 : - +
{ + this.state.isPlaying ? + !this.state.readyToPlay ? + this is the world's world loading screen and we shipped our software with it anyway...

{this.state.loadStatusMessage}
: + : +
+ + +
}
); } }) - -var WatchPage = 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(); - }); - }, - handleBackClicked: function() { - history.back(); - }, - 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, - }) - 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 - ? - :
- - {this.state.controlsShown - ?
-
- -
- -
- Back to LBRY -
-
-
-
- : null} -
- ); - } -}); - -export default WatchPage; diff --git a/ui/scss/component/_video.scss b/ui/scss/component/_video.scss index c6800088c..6ee8efe7e 100644 --- a/ui/scss/component/_video.scss +++ b/ui/scss/component/_video.scss @@ -1,12 +1,55 @@ video { - border: 1px solid red; - object-fill: contain; + object-fit: contain; + box-sizing: border-box; + max-height: 100%; + max-width: 100%; } +.video { + background: #000; + color: white; +} + + .video-embedded { - max-width: 100%; - height: 0; - padding-bottom: 63%; + $height-embedded: $width-page-constrained * 9 / 16; + max-width: $width-page-constrained; + max-height: $height-embedded; video { + height: 100%; } + &.video--hidden { + height: $height-embedded; + } + &.video--active { + background: none; + } +} + +.video__cover { + text-align: center; + height: 100%; + width: 100%; + img { + max-width: 100%; + max-height: 100%; + vertical-align: middle; + } + position: relative; + &:hover { + .video__play-button { @include absolute-center(); } + } +} +.video__play-button { + position: absolute; + width: 100%; + height: 100%; + cursor: pointer; + display: none; + font-size: $spacing-vertical * 3; + color: white; + z-index: 1; + background: $color-black-transparent; + left: 0; + top: 0; } \ No newline at end of file diff --git a/ui/scss/page/_show.scss b/ui/scss/page/_show.scss index c0434ee24..48b82d065 100644 --- a/ui/scss/page/_show.scss +++ b/ui/scss/page/_show.scss @@ -3,4 +3,7 @@ .show-page-media { text-align: center; margin-bottom: $spacing-vertical; + img { + max-width: 100%; + } } \ No newline at end of file diff --git a/ui/scss/page/_watch.scss b/ui/scss/page/_watch.scss index 23fbcc171..59a614d31 100644 --- a/ui/scss/page/_watch.scss +++ b/ui/scss/page/_watch.scss @@ -1,6 +1,3 @@ -.video { - background: #000; -} .video__overlay { position: absolute; From 300264e3644d1303e53c2dacbc4d345a8238dbf4 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 13 Apr 2017 19:39:59 -0400 Subject: [PATCH 43/51] Publish: add Terms of Service agreement field --- ui/js/page/publish.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 90dbe4504..0e6c0478c 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -8,7 +8,7 @@ import lbryio from '../lbryio.js'; import Modal from '../component/modal.js'; var PublishPage = React.createClass({ - _requiredFields: ['meta_title', 'name', 'bid'], + _requiredFields: ['meta_title', 'name', 'bid', 'tos_agree'], _requestPublishReward: function() { lbryio.call('reward', 'list', {}).then(function(userRewards) { @@ -314,6 +314,11 @@ var PublishPage = React.createClass({ newChannelBid: event.target.value, }); }, + handleTOSChange: function(event) { + this.setState({ + TOSAgreed: event.target.checked, + }); + }, handleCreateChannelClick: function (event) { if (this.state.newChannelName.length < 5) { this.refs.newChannelName.showError('LBRY channel names must be at least 4 characters in length.'); @@ -544,6 +549,17 @@ var PublishPage = React.createClass({
: '' }
+
+
+

Terms of Service

+
+
+ I agree to the + } type="checkbox" name="tos_agree" ref={(field) => { this.refs.tos_agree = field }} onChange={this.handleTOSChange} /> +
+
+
From 9b4d8ec2662214d3466c824b0342189c1dbc2f98 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 13 Apr 2017 19:43:17 -0400 Subject: [PATCH 44/51] more show/purchase work --- ui/js/component/auth.js | 2 +- ui/js/component/file-actions.js | 1 - ui/js/page/show.js | 17 +++--- ui/js/page/watch.js | 98 ++++++++++++++++++++++++++------- ui/scss/component/_video.scss | 8 +-- 5 files changed, 90 insertions(+), 36 deletions(-) diff --git a/ui/js/component/auth.js b/ui/js/component/auth.js index 5b3d4d46e..ace7c84df 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth.js @@ -137,7 +137,7 @@ const WelcomeStage = React.createClass({

About Your Reward

-

You earned a reward of 5 LBRY credits, or LBC.

+

You earned a reward of %award% LBRY credits, or LBC.

This reward will show in your Wallet momentarily, likely while you are reading this message.

LBC is used to compensate creators, to publish, and to have say in how the network works.

No need to understand it all just yet! Try watching or downloading something next.

diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index 1c4c1650a..d12e4525f 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -27,7 +27,6 @@ let FileActionsRow = React.createClass({ deleteChecked: false, attemptingDownload: false, attemptingRemove: false, - affirmedPurchase: false } }, onFileInfoUpdate: function(fileInfo) { diff --git a/ui/js/page/show.js b/ui/js/page/show.js index c22dfdeee..a488bb4a2 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -80,21 +80,17 @@ let ShowPage = React.createClass({ }); }, render: function() { - if (this.state.metadata == null) { - return null; - } - - //
const metadata = this.state.uriLookupComplete ? this.state.metadata : null, title = this.state.uriLookupComplete ? metadata.title : this._uri; + console.log(metadata); return (
{ this.state.contentType && this.state.contentType.startsWith('video/') ?
@@ -117,11 +113,12 @@ let ShowPage = React.createClass({ {metadata.description}
- : } -
-
- + :
}
+ { metadata ? +
+ +
: '' }
diff --git a/ui/js/page/watch.js b/ui/js/page/watch.js index bd80c30b2..3e3d163dc 100644 --- a/ui/js/page/watch.js +++ b/ui/js/page/watch.js @@ -2,11 +2,87 @@ import React from 'react'; import {Icon, Thumbnail} from '../component/common.js'; import {Link} from '../component/link.js'; import lbry from '../lbry.js'; +import Modal from '../component/modal.js'; import LoadScreen from '../component/load_screen.js' const fs = require('fs'); const VideoStream = require('videostream'); +export let WatchLink = React.createClass({ + propTypes: { + uri: React.PropTypes.string, + downloadStarted: React.PropTypes.bool, + onGet: React.PropTypes.func + }, + getInitialState: function() { + affirmedPurchase: false + }, + onAffirmPurchase: function() { + lbry.get({uri: this.props.uri}).then((streamInfo) => { + if (streamInfo === null || typeof streamInfo !== 'object') { + this.setState({ + modal: 'timedOut', + attemptingDownload: false, + }); + } + }); + if (this.props.onGet) { + this.props.onGet() + } + }, + onWatchClick: function() { + this.setState({ + loading: true + }); + lbry.getCostInfo(this.props.uri).then(({cost}) => { + lbry.getBalance((balance) => { + if (cost > balance) { + this.setState({ + modal: 'notEnoughCredits', + attemptingDownload: false, + }); + } else if (cost <= 0.01) { + this.onAffirmPurchase() + } else { + this.setState({ + modal: 'affirmPurchase' + }); + } + }); + }); + }, + getInitialState: function() { + return { + modal: null, + loading: false, + }; + }, + closeModal: function() { + this.setState({ + loading: false, + modal: null, + }); + }, + render: function() { + return (
+ + + You don't have enough LBRY credits to pay for this stream. + + + Confirm you want to purchase this bro. + +
); + } +}); + + 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 @@ -27,22 +103,7 @@ export let Video = React.createClass({ controlsShown: false, }; }, - start: function() { - // lbry.getCostInfo(this.props.uri).then(({cost}) => { - // lbry.getBalance((balance) => { - // if (cost > balance) { - // this.setState({ - // modal: 'notEnoughCredits', - // loading: false, - // }); - // } else { - // this.startVideo(); - // } - // }); - // // }); - // - // You don't have enough LBRY credits to pay for this stream. - // + onGet: function() { lbry.get({uri: this.props.uri}).then((fileInfo) => { this._outpoint = fileInfo.outpoint; this.updateLoadStatus(); @@ -127,9 +188,8 @@ export let Video = React.createClass({ !this.state.readyToPlay ? this is the world's world loading screen and we shipped our software with it anyway...

{this.state.loadStatusMessage}
: : -
- - +
+
}
); diff --git a/ui/scss/component/_video.scss b/ui/scss/component/_video.scss index 6ee8efe7e..09775ca39 100644 --- a/ui/scss/component/_video.scss +++ b/ui/scss/component/_video.scss @@ -30,11 +30,9 @@ video { text-align: center; height: 100%; width: 100%; - img { - max-width: 100%; - max-height: 100%; - vertical-align: middle; - } + background-size: auto 100%; + background-position: center center; + background-repeat: no-repeat; position: relative; &:hover { .video__play-button { @include absolute-center(); } From d926967e49dd76e786755794ed66ad8e04ed11d0 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 13 Apr 2017 20:56:58 -0400 Subject: [PATCH 45/51] Correct and simplify metadata processing in dummy pending publishes --- ui/js/component/file-actions.js | 2 +- ui/js/lbry.js | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index d12e4525f..d16e61bab 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -182,7 +182,7 @@ let FileActionsRow = React.createClass({ -

Are you sure you'd like to remove {this.props.metadata.title} from LBRY?

+

Are you sure you'd like to remove {this.props.metadata ? this.props.metadata.title : this.props.uri} from LBRY?

diff --git a/ui/js/lbry.js b/ui/js/lbry.js index b09aabe1f..9e8b1565e 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -11,7 +11,12 @@ const menu = remote.require('./menu/main-menu'); * needed to make a dummy claim or file info object. */ function savePendingPublish({name, channel_name}) { - const lbryUri = uri.buildLbryUri({name, channel_name}, false); + let lbryUri; + if (channel_name) { + lbryUri = uri.buildLbryUri({name: channel_name, path: name}, false); + } else { + lbryUri = uri.buildLbryUri({name: name}, false); + } const pendingPublishes = getLocal('pendingPublishes') || []; const newPendingPublish = { name, channel_name, @@ -61,13 +66,13 @@ function getPendingPublish({name, channel_name, outpoint}) { } function pendingPublishToDummyClaim({channel_name, name, outpoint, claim_id, txid, nout}) { - return {name, outpoint, claim_id, txid, nout, ... channel_name ? {channel_name} : {}}; + return {name, outpoint, claim_id, txid, nout, channel_name}; } function pendingPublishToDummyFileInfo({name, outpoint, claim_id}) { - return {name, outpoint, claim_id, null}; + return {name, outpoint, claim_id, metadata: null}; } - +window.pptdfi = pendingPublishToDummyFileInfo; let lbry = { isConnected: false, @@ -316,14 +321,13 @@ lbry.publish = function(params, fileListedCallback, publishedCallback, errorCall returnedPending = true; if (publishedCallback) { - const {name, channel_name} = params; - savePendingPublish({name, ... channel_name ? {channel_name} : {}}); + savePendingPublish({name: params.name, channel_name: params.channel_name}); publishedCallback(true); } if (fileListedCallback) { const {name, channel_name} = params; - savePendingPublish({name, ... channel_name ? {channel_name} : {}}); + savePendingPublish({name: params.name, channel_name: params.channel_name}); fileListedCallback(true); } }, 2000); From 9532f8b29a69dea508685c93fbde0383e94140f6 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Fri, 14 Apr 2017 13:44:06 -0400 Subject: [PATCH 46/51] Publish: improve state management of names Save the new name as soon as it's entered instead of after resolution, so that it's possible to publish before the results from resolve() and claim_list_mine() come back. --- ui/js/page/publish.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 0e6c0478c..ea3bdb00e 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -140,11 +140,11 @@ var PublishPage = React.createClass({ channel: 'anonymous', newChannelName: '@', newChannelBid: 10, - nameResolved: false, + nameResolved: null, + myClaimExists: null, topClaimValue: 0.0, myClaimValue: 0.0, myClaimMetadata: null, - myClaimExists: null, copyrightNotice: '', otherLicenseDescription: '', otherLicenseUrl: '', @@ -189,31 +189,35 @@ var PublishPage = React.createClass({ return; } + const name = rawName.toLowerCase(); this.setState({ rawName: rawName, + name: name, + nameResolved: null, + myClaimExists: null, }); - const name = rawName.toLowerCase(); lbry.getMyClaim(name, (myClaimInfo) => { - if (name != this.refs.name.getValue().toLowerCase()) { + if (name != this.state.name) { // A new name has been typed already, so bail return; } + + this.setState({ + myClaimExists: !!myClaimInfo, + }); lbry.resolve({uri: name}).then((claimInfo) => { - if (name != this.refs.name.getValue()) { + if (name != this.state.name) { return; } if (!claimInfo) { this.setState({ - name: name, nameResolved: false, - myClaimExists: false, }); } else { const topClaimIsMine = (myClaimInfo && myClaimInfo.claim.amount >= claimInfo.claim.amount); const newState = { - name: name, nameResolved: true, topClaimValue: parseFloat(claimInfo.claim.amount), myClaimExists: !!myClaimInfo, @@ -374,13 +378,13 @@ var PublishPage = React.createClass({ getNameBidHelpText: function() { if (!this.state.name) { return "Select a URL for this publish."; - } else if (!this.state.nameResolved) { + } else if (this.state.nameResolved === false) { return "This URL is unused."; } else if (this.state.myClaimExists) { return "You have already used this URL. Publishing to it again will update your previous publish." } else if (this.state.topClaimValue) { return A deposit of at least {this.state.topClaimValue} {this.state.topClaimValue == 1 ? 'credit ' : 'credits '} - is required to win {this.state.name}. However, you can still get a perminent URL for any amount. + is required to win {this.state.name}. However, you can still get a permanent URL for any amount. } else { return ''; } @@ -447,7 +451,7 @@ var PublishPage = React.createClass({

Access

- How much does this content cost ? + How much does this content cost?
From 8d67d36ad9607c3c2997b1f162e434bd38e004c5 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 14 Apr 2017 18:23:42 -0400 Subject: [PATCH 47/51] views, rewards, fixes for no api, other fixes --- app/main.js | 2 +- ui/js/component/auth.js | 12 +++++++----- ui/js/component/file-actions.js | 2 -- ui/js/component/form.js | 2 +- ui/js/component/link.js | 11 ++++++----- ui/js/lbryio.js | 15 ++++++++++++++- ui/js/main.js | 3 +-- ui/js/page/discover.js | 25 ++++++++++++++++--------- ui/js/page/publish.js | 2 +- ui/js/page/rewards.js | 5 ++++- ui/js/page/show.js | 3 +-- ui/js/page/watch.js | 7 +++++++ ui/js/rewards.js | 4 ++++ 13 files changed, 63 insertions(+), 30 deletions(-) diff --git a/app/main.js b/app/main.js index e2fad0905..4b9a4664f 100644 --- a/app/main.js +++ b/app/main.js @@ -62,7 +62,7 @@ function getPidsForProcessName(name) { } function createWindow () { - win = new BrowserWindow({backgroundColor: '#155B4A', minWidth: 800, minHeight: 1000 }) //$color-primary + win = new BrowserWindow({backgroundColor: '#155B4A', minWidth: 800, minHeight: 600 }) //$color-primary win.maximize() win.webContents.openDevTools(); win.loadURL(`file://${__dirname}/dist/index.html`) diff --git a/ui/js/component/auth.js b/ui/js/component/auth.js index ace7c84df..197fc0b38 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth.js @@ -5,6 +5,7 @@ import Modal from './modal.js'; import ModalPage from './modal-page.js'; import {Link, RewardLink} from '../component/link.js'; import {FormField, FormRow} from '../component/form.js'; +import {CreditAmount} from '../component/common.js'; import rewards from '../rewards.js'; @@ -114,9 +115,10 @@ const WelcomeStage = React.createClass({ } }, onRewardClaim: function(reward) { + console.log(reward); this.setState({ hasReward: true, - rewardAmount: reward + rewardAmount: reward.amount }) }, render: function() { @@ -127,8 +129,8 @@ const WelcomeStage = React.createClass({

Welcome to LBRY.

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 -- you -- and no one else.

-

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

+

Below, LBRY is controlled by its users -- you -- through the power of blockchain and decentralization.

+

Thanks for making it possible! Here's a nickel, kid.

@@ -137,8 +139,8 @@ const WelcomeStage = React.createClass({

About Your Reward

-

You earned a reward of %award% LBRY credits, or LBC.

-

This reward will show in your Wallet momentarily, likely while you are reading this message.

+

You earned a reward of LBRY credits, or LBC.

+

This reward will show in your Wallet momentarily, probably while you are reading this message.

LBC is used to compensate creators, to publish, and to have say in how the network works.

No need to understand it all just yet! Try watching or downloading something next.

diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index d16e61bab..fff8191e2 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -43,8 +43,6 @@ let FileActionsRow = React.createClass({ attemptingRemove: false }); lbry.getCostInfo(this.props.uri).then(({cost}) => { - console.log(cost); - console.log(this.props.uri); lbry.getBalance((balance) => { if (cost > balance) { this.setState({ diff --git a/ui/js/component/form.js b/ui/js/component/form.js index ef8a7169b..f75310c92 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -96,7 +96,7 @@ export let FormField = React.createClass({ export let FormRow = React.createClass({ _fieldRequiredText: 'This field is required', propTypes: { - label: React.PropTypes.string, + label: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element]) // helper: React.PropTypes.html, }, getInitialState: function() { diff --git a/ui/js/component/link.js b/ui/js/component/link.js index 88e60bf7e..55c0060dd 100644 --- a/ui/js/component/link.js +++ b/ui/js/component/link.js @@ -92,20 +92,20 @@ export let RewardLink = React.createClass({ this.setState({ pending: true }) - rewards.claimReward(this.props.type).then(function(reward) { + rewards.claimReward(this.props.type).then((reward) => { this.setState({ pending: false, errorMessage: null }) if (this.props.onRewardClaim) { - this.props.onRewardClaim(); + this.props.onRewardClaim(reward); } - }.bind(this)).catch(function(error) { + }).catch((error) => { this.setState({ errorMessage: error.message, pending: false }) - }.bind(this)) + }) }, clearError: function() { if (this.props.onRewardFailure) { @@ -120,7 +120,8 @@ export let RewardLink = React.createClass({
{this.props.claimed ? Reward claimed. - : } + : } {this.state.errorMessage ? {this.state.errorMessage} diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 06dbd46d9..53ba92f8a 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -6,7 +6,8 @@ const querystring = require('querystring'); const lbryio = { _accessToken: getLocal('accessToken'), _authenticationPromise: null, - _user : null + _user : null, + enabled: false }; const CONNECTION_STRING = 'http://localhost:8080/'; @@ -25,6 +26,10 @@ const mocks = { lbryio.call = function(resource, action, params={}, method='get') { return new Promise((resolve, reject) => { + if (!lbryio.enabled) { + reject(new Error("LBRY interal API is disabled")) + return + } /* temp code for mocks */ if (`${resource}.${action}` in mocks) { resolve(mocks[`${resource}.${action}`](params)); @@ -90,6 +95,14 @@ lbryio.setAccessToken = (token) => { } lbryio.authenticate = function() { + if (!lbryio.enabled) { + return new Promise((resolve, reject) => { + resolve({ + ID: 1, + HasVerifiedEmail: true + }) + }) + } if (lbryio._authenticationPromise === null) { lbryio._authenticationPromise = new Promise((resolve, reject) => { lbry.status().then(({installation_id}) => { diff --git a/ui/js/main.js b/ui/js/main.js index d31b4c7d5..610ca8594 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -30,8 +30,7 @@ let init = function() { function onDaemonReady() { window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again - // - ReactDOM.render(
, canvas) + ReactDOM.render(
{ lbryio.enabled ? : '' }
, canvas) } if (window.sessionStorage.getItem('loaded') == 'y') { diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js index b3d427cfe..8aadd2df4 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover.js @@ -81,6 +81,7 @@ var FeaturedContent = React.createClass({ getInitialState: function() { return { featuredUris: {}, + failed: false }; }, componentWillMount: function() { @@ -92,19 +93,25 @@ var FeaturedContent = React.createClass({ } }) this.setState({ featuredUris: featuredUris }); + }, () => { + this.setState({ + failed: true + }) }); }, render: function() { return ( -
- { - Object.keys(this.state.featuredUris).map(function(category) { - return this.state.featuredUris[category].length ? - : - ''; - }.bind(this)) - } -
+ this.state.failed ? +
Failed to load landing content.
: +
+ { + Object.keys(this.state.featuredUris).map(function(category) { + return this.state.featuredUris[category].length ? + : + ''; + }.bind(this)) + } +
); } }); diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index ea3bdb00e..3dfc8ac1e 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -27,7 +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) + rewards.claimReward(rewards.TYPE_FIRST_CHANNEL).then(() => {}, () => {}) this.setState({ channels: channels, ... channel ? {channel} : {} diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js index 62c5d0497..18e936aee 100644 --- a/ui/js/page/rewards.js +++ b/ui/js/page/rewards.js @@ -42,6 +42,7 @@ var RewardsPage = React.createClass({ getInitialState: function() { return { userRewards: null, + failed: null }; }, loadRewards: function() { @@ -49,6 +50,8 @@ var RewardsPage = React.createClass({ this.setState({ userRewards: userRewards, }); + }, () => { + this.setState({failed: true }) }); }, render: function() { @@ -56,7 +59,7 @@ var RewardsPage = React.createClass({
{!this.state.userRewards - ? null + ? (this.state.failed ?
Failed to load rewards.
: '') : this.state.userRewards.map(({RewardType, RewardTitle, RewardDescription, TransactionID, RewardAmount}) => { return ; })} diff --git a/ui/js/page/show.js b/ui/js/page/show.js index a488bb4a2..5ad238ae8 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -83,8 +83,7 @@ let ShowPage = React.createClass({ const metadata = this.state.uriLookupComplete ? this.state.metadata : null, title = this.state.uriLookupComplete ? metadata.title : this._uri; - - console.log(metadata); + return (
diff --git a/ui/js/page/watch.js b/ui/js/page/watch.js index 3e3d163dc..833cd5775 100644 --- a/ui/js/page/watch.js +++ b/ui/js/page/watch.js @@ -3,6 +3,7 @@ import {Icon, Thumbnail} from '../component/common.js'; import {Link} from '../component/link.js'; import lbry from '../lbry.js'; import Modal from '../component/modal.js'; +import lbryio from '../lbryio.js'; import LoadScreen from '../component/load_screen.js' const fs = require('fs'); @@ -25,6 +26,12 @@ export let WatchLink = React.createClass({ attemptingDownload: false, }); } + + lbryio.call('file', 'view', { + uri: this.props.uri, + outpoint: streamInfo.outpoint, + claimId: streamInfo.claim_id + }) }); if (this.props.onGet) { this.props.onGet() diff --git a/ui/js/rewards.js b/ui/js/rewards.js index ec043de02..8b66ce371 100644 --- a/ui/js/rewards.js +++ b/ui/js/rewards.js @@ -24,6 +24,10 @@ rewards.TYPE_NEW_DEVELOPER = "new_developer", rewards.claimReward = function (type) { function requestReward(resolve, reject, params) { + if (!lbryio.enabled) { + reject(new Error("Rewards are not enabled.")) + return; + } lbryio.call('reward', 'new', params, 'post').then(({RewardAmount}) => { const message = rewardMessage(type, RewardAmount), From e9c8abd3071f2763d8a627e38ff7fb7a1d54b99b Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Mon, 17 Apr 2017 08:27:39 -0400 Subject: [PATCH 48/51] rewards flow changes --- ui/js/component/common.js | 5 ++- ui/js/component/file-tile.js | 10 ++--- ui/js/lbryio.js | 2 +- ui/js/page/file-list.js | 22 ++++++++++ ui/js/page/publish.js | 16 +------ ui/js/page/rewards.js | 1 + ui/js/page/show.js | 2 +- ui/js/page/watch.js | 4 ++ ui/js/rewards.js | 73 ++++++++++++++++++++++--------- ui/scss/_global.scss | 5 ++- ui/scss/component/_file-tile.scss | 9 +++- ui/scss/component/_video.scss | 8 ++-- 12 files changed, 106 insertions(+), 51 deletions(-) diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 78bb6522e..d8b0fc052 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -186,6 +186,9 @@ export let Thumbnail = React.createClass({ this._isMounted = false; }, render: function() { - return + const className = this.props.className ? this.props.className : '', + otherProps = Object.assign({}, this.props) + delete otherProps.className; + return }, }); diff --git a/ui/js/component/file-tile.js b/ui/js/component/file-tile.js index 172dea636..b2477a7be 100644 --- a/ui/js/component/file-tile.js +++ b/ui/js/component/file-tile.js @@ -81,14 +81,14 @@ export let FileTileStream = React.createClass({ return (
-
+
- { !this.props.hidePrice - ? - : null}

- + {isConfirmed ? metadata.description : This file is pending confirmation.} diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 53ba92f8a..085c3097b 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -7,7 +7,7 @@ const lbryio = { _accessToken: getLocal('accessToken'), _authenticationPromise: null, _user : null, - enabled: false + enabled: true }; const CONNECTION_STRING = 'http://localhost:8080/'; diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index 3c337565f..e746f9d77 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -4,6 +4,8 @@ import uri from '../uri.js'; import {Link} from '../component/link.js'; import {FormField} from '../component/form.js'; import {FileTileStream} from '../component/file-tile.js'; +import rewards from '../rewards.js'; +import lbryio from '../lbryio.js'; import {BusyMessage, Thumbnail} from '../component/common.js'; @@ -32,6 +34,9 @@ export let FileListDownloaded = React.createClass({ }); }); }, + componentWillUnmount: function() { + this._isMounted = false; + }, render: function() { if (this.state.fileInfos === null) { return ( @@ -63,8 +68,22 @@ export let FileListPublished = React.createClass({ fileInfos: null, }; }, + _requestPublishReward: function() { + lbryio.call('reward', 'list', {}).then(function(userRewards) { + //already rewarded + if (userRewards.filter(function (reward) { + return reward.RewardType == rewards.TYPE_FIRST_PUBLISH && reward.TransactionID; + }).length) { + return; + } + else { + rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {}) + } + }); + }, componentDidMount: function () { this._isMounted = true; + this._requestPublishReward(); document.title = "Published Files"; lbry.claim_list_mine().then((claimInfos) => { @@ -80,6 +99,9 @@ export let FileListPublished = React.createClass({ }); }); }, + componentWillUnmount: function() { + this._isMounted = false; + }, render: function () { if (this.state.fileInfos === null) { return ( diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 3dfc8ac1e..b424e07ee 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -10,19 +10,6 @@ import Modal from '../component/modal.js'; var PublishPage = React.createClass({ _requiredFields: ['meta_title', 'name', 'bid', 'tos_agree'], - _requestPublishReward: function() { - lbryio.call('reward', 'list', {}).then(function(userRewards) { - //already rewarded - if (userRewards.filter(function (reward) { - return reward.RewardType == rewards.TYPE_FIRST_PUBLISH && reward.TransactionID; - }).length) { - return; - } - else { - rewards.claimReward(rewards.TYPE_FIRST_PUBLISH) - } - }); - }, _updateChannelList: function(channel) { // 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) @@ -361,7 +348,6 @@ var PublishPage = React.createClass({ }, componentWillMount: function() { this._updateChannelList(); - // this._requestPublishReward(); }, componentDidMount: function() { document.title = "Publish"; @@ -574,7 +560,7 @@ var PublishPage = React.createClass({

Your file has been published to LBRY at the address lbry://{this.state.name}!

- You will now be taken to your My Files page, where your newly published file will be listed. The file will take a few minutes to appear for other LBRY users; until then it will be listed as "pending." +

The file will take a few minutes to appear for other LBRY users. Until then it will be listed as "pending" under your published files.

diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js index 18e936aee..0e0ac80e4 100644 --- a/ui/js/page/rewards.js +++ b/ui/js/page/rewards.js @@ -55,6 +55,7 @@ var RewardsPage = React.createClass({ }); }, render: function() { + console.log(this.state.userRewards); return (
diff --git a/ui/js/page/show.js b/ui/js/page/show.js index 5ad238ae8..d38e9dc75 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -83,7 +83,7 @@ let ShowPage = React.createClass({ const metadata = this.state.uriLookupComplete ? this.state.metadata : null, title = this.state.uriLookupComplete ? metadata.title : this._uri; - + return (
diff --git a/ui/js/page/watch.js b/ui/js/page/watch.js index 833cd5775..76e96cada 100644 --- a/ui/js/page/watch.js +++ b/ui/js/page/watch.js @@ -4,6 +4,7 @@ import {Link} from '../component/link.js'; import lbry from '../lbry.js'; import Modal from '../component/modal.js'; import lbryio from '../lbryio.js'; +import rewards from '../rewards.js'; import LoadScreen from '../component/load_screen.js' const fs = require('fs'); @@ -182,6 +183,9 @@ export let Video = React.createClass({ return fs.createReadStream(status.download_path, opts) } }; + + rewards.claimNextPurchaseReward() + var elem = this.refs.video; var videostream = VideoStream(mediaFile, elem); elem.play(); diff --git a/ui/js/rewards.js b/ui/js/rewards.js index 8b66ce371..168070627 100644 --- a/ui/js/rewards.js +++ b/ui/js/rewards.js @@ -3,12 +3,13 @@ import lbryio from './lbryio.js'; function rewardMessage(type, amount) { return { - new_developer: "You received ${amount} for registering as a new developer.", - new_user: `You received ${amount} LBC new user reward.`, - confirm_email: "You received ${amount} LBC for verifying your email address.", - first_channel: "You received ${amount} LBC for creating a publisher identity.", - first_purchase: "You received ${amount} LBC for making your first purchase.", - first_publish: "You received ${amount} LBC for making your first publication.", + new_developer: `You earned ${amount} for registering as a new developer.`, + new_user: `You earned ${amount} LBC new user reward.`, + confirm_email: `You earned ${amount} LBC for verifying your email address.`, + new_channel: `You earned ${amount} LBC for creating a publisher identity.`, + first_stream: `You earned ${amount} LBC for streaming your first video.`, + many_downloads: `You earned ${amount} LBC for downloading some of the things.`, + first_publish: `You earned ${amount} LBC for making your first publication.`, }[type]; } @@ -17,8 +18,9 @@ const rewards = {}; rewards.TYPE_NEW_DEVELOPER = "new_developer", rewards.TYPE_NEW_USER = "new_user", rewards.TYPE_CONFIRM_EMAIL = "confirm_email", - rewards.TYPE_FIRST_CHANNEL = "first_channel", - rewards.TYPE_FIRST_PURCHASE = "first_purchase", + rewards.TYPE_FIRST_CHANNEL = "new_channel", + rewards.TYPE_FIRST_STREAM = "first_stream", + rewards.TYPE_MANY_DOWNLOADS = "many_downloads", rewards.TYPE_FIRST_PUBLISH = "first_publish"; rewards.claimReward = function (type) { @@ -62,9 +64,13 @@ rewards.claimReward = function (type) { switch (type) { case rewards.TYPE_FIRST_CHANNEL: - lbry.claim_list_mine().then(function(channels) { - if (channels.length) { - params.transaction_id = channels[0].txid; + lbry.claim_list_mine().then(function(claims) { + let claim = claims.find(function(claim) { + return claim.name.length && claim.name[0] == '@' && claim.txid.length + }) + console.log(claim); + if (claim) { + params.transaction_id = claim.txid; requestReward(resolve, reject, params) } else { reject(new Error("Please create a channel identity first.")) @@ -72,18 +78,24 @@ rewards.claimReward = function (type) { }).catch(reject) break; - case 'first_purchase': - // 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; + case rewards.TYPE_FIRST_PUBLISH: + lbry.claim_list_mine().then((claims) => { + let claim = claims.find(function(claim) { + return claim.name.length && claim.name[0] != '@' && claim.txid.length + }) + if (claim) { + params.transaction_id = claim.txid + requestReward(resolve, reject, params) + } else { + reject(claims.length ? + new Error("Please publish something and wait for confirmation by the network to claim this reward.") : + new Error("Please publish something to claim this reward.")) + } + }).catch(reject) break; + case rewards.TYPE_FIRST_STREAM: + case rewards.TYPE_NEW_USER: default: requestReward(resolve, reject, params); } @@ -91,4 +103,23 @@ rewards.claimReward = function (type) { }); } +rewards.claimNextPurchaseReward = function() { + let types = {} + types[rewards.TYPE_FIRST_STREAM] = false + types[rewards.TYPE_MANY_DOWNLOADS] = false + lbryio.call('reward', 'list', {}).then((userRewards) => { + userRewards.forEach((reward) => { + if (types[reward.RewardType] === false && reward.TransactionID) { + types[reward.RewardType] = true + } + }) + let unclaimedType = Object.keys(types).find((type) => { + return types[type] === false; + }) + if (unclaimedType) { + rewards.claimReward(unclaimedType); + } + }, () => { }); +} + export default rewards; \ No newline at end of file diff --git a/ui/scss/_global.scss b/ui/scss/_global.scss index 68533b206..d829d8245 100644 --- a/ui/scss/_global.scss +++ b/ui/scss/_global.scss @@ -28,10 +28,11 @@ $mobile-width-threshold: 801px; $max-content-width: 1000px; $max-text-width: 660px; +$width-page-constrained: 800px; + $height-header: $spacing-vertical * 2.5; $height-button: $spacing-vertical * 1.5; - -$width-page-constrained: 800px; +$height-video-embedded: $width-page-constrained * 9 / 16; $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/component/_file-tile.scss b/ui/scss/component/_file-tile.scss index 9ff9b2c75..eb768bbf0 100644 --- a/ui/scss/component/_file-tile.scss +++ b/ui/scss/component/_file-tile.scss @@ -1,6 +1,8 @@ @import "../global"; +$height-file-tile: $spacing-vertical * 8; .file-tile__row { + height: $height-file-tile; .credit-amount { float: right; } @@ -12,12 +14,17 @@ .file-tile__thumbnail { max-width: 100%; - max-height: $spacing-vertical * 7; + max-height: $height-file-tile; vertical-align: middle; display: block; margin-left: auto; margin-right: auto; } +.file-tile__thumbnail-container +{ + height: $height-file-tile; + @include absolute-center(); +} .file-tile__title { font-weight: bold; diff --git a/ui/scss/component/_video.scss b/ui/scss/component/_video.scss index 09775ca39..c41815dca 100644 --- a/ui/scss/component/_video.scss +++ b/ui/scss/component/_video.scss @@ -12,17 +12,17 @@ video { .video-embedded { - $height-embedded: $width-page-constrained * 9 / 16; max-width: $width-page-constrained; - max-height: $height-embedded; + max-height: $height-video-embedded; + height: $height-video-embedded; video { height: 100%; } &.video--hidden { - height: $height-embedded; + height: $height-video-embedded; } &.video--active { - background: none; + /*background: none;*/ } } From 581be8c429ac5b677d1798689e6124fb79aac4bc Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Mon, 17 Apr 2017 09:06:13 -0400 Subject: [PATCH 49/51] disable lbryio --- ui/js/lbryio.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 085c3097b..53ba92f8a 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -7,7 +7,7 @@ const lbryio = { _accessToken: getLocal('accessToken'), _authenticationPromise: null, _user : null, - enabled: true + enabled: false }; const CONNECTION_STRING = 'http://localhost:8080/'; From a937534a8bf9220dabadf155550ccdbb4f344724 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Mon, 17 Apr 2017 09:46:41 -0400 Subject: [PATCH 50/51] disable lbryio --- ui/js/lbryio.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 53ba92f8a..53b49271b 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -8,7 +8,7 @@ const lbryio = { _authenticationPromise: null, _user : null, enabled: false -}; +}; const CONNECTION_STRING = 'http://localhost:8080/'; From 6603bb4cd26faaaeb1d1aae1cf4b67ae6a85b984 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Mon, 17 Apr 2017 10:01:33 -0400 Subject: [PATCH 51/51] allow discover if lbryio is disabled --- ui/js/lbryio.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 53b49271b..d662354e6 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -8,9 +8,9 @@ const lbryio = { _authenticationPromise: null, _user : null, enabled: false -}; +}; -const CONNECTION_STRING = 'http://localhost:8080/'; +const CONNECTION_STRING = 'https://api.lbry.io/'; const mocks = { 'reward_type.get': ({name}) => { @@ -26,7 +26,7 @@ const mocks = { lbryio.call = function(resource, action, params={}, method='get') { return new Promise((resolve, reject) => { - if (!lbryio.enabled) { + if (!lbryio.enabled && (resource != 'discover' || action != 'list')) { reject(new Error("LBRY interal API is disabled")) return }