Merge branch 'master' into i18n

This commit is contained in:
Mayesters 2017-05-31 12:48:49 +02:00
commit 31190ea5b0
22 changed files with 147 additions and 84 deletions

View file

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.11.5 current_version = 0.11.7
commit = True commit = True
tag = True tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)((?P<release>[a-z]+)(?P<candidate>\d+))? parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)((?P<release>[a-z]+)(?P<candidate>\d+))?

View file

@ -27,6 +27,33 @@ Web UI version numbers should always match the corresponding version of LBRY App
* *
* *
## [0.11.7] - 2017-05-30
### Changed
* Video player switched from plyr to render-media
### Fixed
* Video player should behave better on streaming
* Daemon times out more quickly when it cannot start
* Connection should fail more cleanly, rather than get stuck entirely
* Closing modal dialogs was broken on some download and stream errors
* Discover landing page improperly showed loading error when it was loading correctly
## [0.11.6] - 2017-05-29
### Changed
* Do not use a separate claim cache for publishes
### Fixed
* Upgrade process should now works on Windows
* Crudely handle failed publishes missing outpoints
## [0.11.5] - 2017-05-28 ## [0.11.5] - 2017-05-28
### Fixed ### Fixed

View file

@ -1,6 +1,6 @@
{ {
"name": "LBRY", "name": "LBRY",
"version": "0.11.5", "version": "0.11.7",
"main": "main.js", "main": "main.js",
"description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.",
"author": { "author": {

View file

@ -209,7 +209,7 @@ export function doLoadVideo(uri) {
} }
} }
export function doPurchaseUri(uri) { export function doPurchaseUri(uri, purchaseModalName) {
return function(dispatch, getState) { return function(dispatch, getState) {
const state = getState() const state = getState()
const balance = selectBalance(state) const balance = selectBalance(state)
@ -244,7 +244,7 @@ export function doPurchaseUri(uri) {
if (cost > balance) { if (cost > balance) {
dispatch(doOpenModal('notEnoughCredits')) dispatch(doOpenModal('notEnoughCredits'))
} else { } else {
dispatch(doOpenModal('affirmPurchase')) dispatch(doOpenModal(purchaseModalName))
} }
return Promise.resolve() return Promise.resolve()
@ -274,16 +274,16 @@ export function doFetchClaimsByChannel(uri) {
} }
} }
export function doClaimListMine() { export function doFetchClaimListMine() {
return function(dispatch, getState) { return function(dispatch, getState) {
dispatch({ dispatch({
type: types.CLAIM_LIST_MINE_STARTED type: types.FETCH_CLAIM_LIST_MINE_STARTED
}) })
lbry.claim_list_mine().then((claims) => { lbry.claim_list_mine().then((claims) => {
dispatch({ dispatch({
type: types.CLAIM_LIST_MINE_COMPLETED, type: types.FETCH_CLAIM_LIST_MINE_COMPLETED,
data: { data: {
claims claims
} }

View file

@ -1,7 +1,7 @@
import * as types from 'constants/action_types' import * as types from 'constants/action_types'
import lbry from 'lbry' import lbry from 'lbry'
import { import {
doClaimListMine doFetchClaimListMine
} from 'actions/content' } from 'actions/content'
import { import {
selectClaimsByUri, selectClaimsByUri,
@ -110,7 +110,7 @@ export function doFetchFileInfosAndPublishedClaims() {
isFileInfoListPending = selectFileListIsPending(state) isFileInfoListPending = selectFileListIsPending(state)
if (isClaimListMinePending === undefined) { if (isClaimListMinePending === undefined) {
dispatch(doClaimListMine()) dispatch(doFetchClaimListMine())
} }
if (isFileInfoListPending === undefined) { if (isFileInfoListPending === undefined) {

View file

@ -66,7 +66,7 @@ const perform = (dispatch) => ({
dispatch(doDeleteFile(fileInfo, deleteFromComputer)) dispatch(doDeleteFile(fileInfo, deleteFromComputer))
}, },
openModal: (modal) => dispatch(doOpenModal(modal)), openModal: (modal) => dispatch(doOpenModal(modal)),
startDownload: (uri) => dispatch(doPurchaseUri(uri)), startDownload: (uri) => dispatch(doPurchaseUri(uri, 'affirmPurchase')),
loadVideo: (uri) => dispatch(doLoadVideo(uri)), loadVideo: (uri) => dispatch(doLoadVideo(uri)),
}) })

View file

@ -122,7 +122,7 @@ class FileActions extends React.Component {
<DropDownMenuItem key={1} onClick={() => openModal('confirmRemove')} label={__("Remove...")} /> <DropDownMenuItem key={1} onClick={() => openModal('confirmRemove')} label={__("Remove...")} />
</DropDownMenu> : '' } </DropDownMenu> : '' }
<Modal type="confirm" isOpen={modal == 'affirmPurchase'} <Modal type="confirm" isOpen={modal == 'affirmPurchase'}
contentLabel={__("Confirm Purchase")} onConfirmed={this.onAffirmPurchase.bind(this)} onAborted={this.props.closeModal}> contentLabel="Confirm Purchase" onConfirmed={this.onAffirmPurchase.bind(this)} onAborted={closeModal}>
{__("This will purchase")} <strong>{title}</strong> {__("for")} <strong><FilePrice uri={uri} look="plain" /></strong> {__("credits")}. {__("This will purchase")} <strong>{title}</strong> {__("for")} <strong><FilePrice uri={uri} look="plain" /></strong> {__("credits")}.
</Modal> </Modal>
<Modal isOpen={modal == 'notEnoughCredits'} contentLabel={__("Not enough credits")} <Modal isOpen={modal == 'notEnoughCredits'} contentLabel={__("Not enough credits")}

View file

@ -31,9 +31,11 @@ class LoadScreen extends React.Component {
<img src={imgSrc} alt="LBRY"/> <img src={imgSrc} alt="LBRY"/>
<div className="load-screen__message"> <div className="load-screen__message">
<h3> <h3>
<BusyMessage message={this.props.message} /> {!this.props.isWarning ?
<BusyMessage message={this.props.message} /> :
<span><Icon icon="icon-warning" />{' ' + this.props.message}</span> }
</h3> </h3>
{this.props.isWarning ? <Icon icon="icon-warning" /> : null} <span className={'load-screen__details ' + (this.props.isWarning ? 'load-screen__details--warning' : '')}>{this.props.details}</span> <span className={'load-screen__details ' + (this.props.isWarning ? 'load-screen__details--warning' : '')}>{this.props.details}</span>
</div> </div>
</div> </div>
); );

View file

@ -12,7 +12,8 @@ export class SplashScreen extends React.Component {
super(props); super(props);
this.state = { this.state = {
details: __('Starting daemon'), details: 'Starting daemon',
message: "Connecting",
isLagging: false, isLagging: false,
}; };
} }
@ -29,11 +30,12 @@ export class SplashScreen extends React.Component {
// TODO: This is a hack, and the logic should live in the daemon // TODO: This is a hack, and the logic should live in the daemon
// to give us a better sense of when we are actually started // to give us a better sense of when we are actually started
this.setState({ this.setState({
details: __('Waiting for name resolution'), message: "Testing Network",
details: "Waiting for name resolution",
isLagging: false isLagging: false
}); });
lbry.resolve({uri: 'lbry://one'}).then(() => { lbry.resolve({uri: "lbry://one"}).then(() => {
this.props.onLoadDone(); this.props.onLoadDone();
}); });
return; return;
@ -48,21 +50,19 @@ export class SplashScreen extends React.Component {
} }
componentDidMount() { componentDidMount() {
lbry.connect().then((isConnected) => { lbry.connect()
if (isConnected) { .then(() => { this.updateStatus() })
this.updateStatus(); .catch(() => {
} else {
this.setState({ this.setState({
isLagging: true, isLagging: true,
message: __("Failed to connect to LBRY"), message: "Connection Failure",
details: __("LBRY was unable to start and connect properly.") details: "Try closing all LBRY processes and starting again. If this still happpens, your anti-virus software or firewall may be preventing LBRY from connecting. Contact hello@lbry.io if you think this is a software bug."
}) })
} })
})
} }
render() { render() {
return <LoadScreen message={this.props.message} details={this.state.details} isWarning={this.state.isLagging} /> return <LoadScreen message={this.state.message} details={this.state.details} isWarning={this.state.isLagging} />
} }
} }

View file

@ -47,7 +47,7 @@ const makeSelect = () => {
const perform = (dispatch) => ({ const perform = (dispatch) => ({
loadVideo: (uri) => dispatch(doLoadVideo(uri)), loadVideo: (uri) => dispatch(doLoadVideo(uri)),
purchaseUri: (uri) => dispatch(doPurchaseUri(uri)), purchaseUri: (uri) => dispatch(doPurchaseUri(uri, 'affirmPurchaseAndPlay')),
closeModal: () => dispatch(doCloseModal()), closeModal: () => dispatch(doCloseModal()),
}) })

View file

@ -52,28 +52,25 @@ class VideoPlayButton extends React.Component {
className="video__play-button" className="video__play-button"
icon="icon-play" icon="icon-play"
onClick={this.onWatchClick.bind(this)} /> onClick={this.onWatchClick.bind(this)} />
{modal} <Modal contentLabel="Not enough credits" isOpen={modal == 'notEnoughCredits'} onConfirmed={closeModal}>
<Modal contentLabel={__("Not enough credits")} isOpen={modal == 'notEnoughCredits'} onConfirmed={() => { this.closeModal() }}>
{__("You don't have enough LBRY credits to pay for this stream.")} {__("You don't have enough LBRY credits to pay for this stream.")}
</Modal> </Modal>
<Modal <Modal
type="confirm" type="confirm"
isOpen={modal == 'affirmPurchase'} isOpen={modal == 'affirmPurchaseAndPlay'}
contentLabel={__("Confirm Purchase")} contentLabel={__("Confirm Purchase")}
onConfirmed={this.onPurchaseConfirmed.bind(this)} onConfirmed={this.onPurchaseConfirmed.bind(this)}
onAborted={closeModal}> onAborted={closeModal}>
{__("This will purchase")} <strong>{title}</strong> {__("for")} <strong><FilePrice uri={uri} look="plain" /></strong> {__("credits")}. {__("This will purchase")} <strong>{title}</strong> {__("for")} <strong><FilePrice uri={uri} look="plain" /></strong> {__("credits")}.
</Modal> </Modal>
<Modal <Modal
isOpen={modal == 'timedOut'} onConfirmed={() => { this.closeModal() }} contentLabel="Timed Out"> isOpen={modal == 'timedOut'} onConfirmed={closeModal} contentLabel="Timed Out">
{__("Sorry, your download timed out :(")} {__("Sorry, your download timed out :(")}
</Modal> </Modal>
</div>); </div>);
} }
} }
const plyr = require('plyr')
class Video extends React.Component { class Video extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
@ -114,7 +111,7 @@ class Video extends React.Component {
isPlaying || isLoading ? isPlaying || isLoading ?
(!isReadyToPlay ? (!isReadyToPlay ?
<span>{__("this is the world's worst loading screen and we shipped our software with it anyway...")} <br /><br />{loadStatusMessage}</span> : <span>{__("this is the world's worst loading screen and we shipped our software with it anyway...")} <br /><br />{loadStatusMessage}</span> :
<VideoPlayer poster={metadata.thumbnail} autoplay={isPlaying} downloadPath={fileInfo.download_path} />) : <VideoPlayer filename={fileInfo.file_name} poster={metadata.thumbnail} downloadPath={fileInfo.download_path} />) :
<div className="video__cover" style={{backgroundImage: 'url("' + metadata.thumbnail + '")'}}> <div className="video__cover" style={{backgroundImage: 'url("' + metadata.thumbnail + '")'}}>
<VideoPlayButton startPlaying={this.startPlaying.bind(this)} {...this.props} /> <VideoPlayButton startPlaying={this.startPlaying.bind(this)} {...this.props} />
</div> </div>
@ -123,18 +120,28 @@ class Video extends React.Component {
} }
} }
const from = require('from2')
const player = require('render-media')
const fs = require('fs')
class VideoPlayer extends React.Component { class VideoPlayer extends React.Component {
componentDidMount() { componentDidMount() {
const elem = this.refs.video const elem = this.refs.video
const { const {
autoplay,
downloadPath, downloadPath,
contentType, contentType,
filename,
} = this.props } = this.props
const players = plyr.setup(elem) const file = {
if (autoplay) { name: filename,
players[0].play() createReadStream: (opts) => {
return fs.createReadStream(downloadPath, opts)
}
} }
player.render(file, elem, {
autoplay: true,
controls: true,
})
} }
render() { render() {
@ -145,7 +152,7 @@ class VideoPlayer extends React.Component {
} = this.props } = this.props
return ( return (
<video controls id="video" ref="video" style={{backgroundImage: "url('" + poster + "')"}} > <video controls ref="video" style={{backgroundImage: "url('" + poster + "')"}} >
<source src={downloadPath} type={contentType} /> <source src={downloadPath} type={contentType} />
</video> </video>
) )

View file

@ -40,8 +40,8 @@ export const RESOLVE_URI_COMPLETED = 'RESOLVE_URI_COMPLETED'
export const RESOLVE_URI_CANCELED = 'RESOLVE_URI_CANCELED' export const RESOLVE_URI_CANCELED = 'RESOLVE_URI_CANCELED'
export const FETCH_CHANNEL_CLAIMS_STARTED = 'FETCH_CHANNEL_CLAIMS_STARTED' export const FETCH_CHANNEL_CLAIMS_STARTED = 'FETCH_CHANNEL_CLAIMS_STARTED'
export const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED' export const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED'
export const CLAIM_LIST_MINE_STARTED = 'CLAIM_LIST_MINE_STARTED' export const FETCH_CLAIM_LIST_MINE_STARTED = 'FETCH_CLAIM_LIST_MINE_STARTED'
export const CLAIM_LIST_MINE_COMPLETED = 'CLAIM_LIST_MINE_COMPLETED' export const FETCH_CLAIM_LIST_MINE_COMPLETED = 'FETCH_CLAIM_LIST_MINE_COMPLETED'
export const FILE_LIST_STARTED = 'FILE_LIST_STARTED' export const FILE_LIST_STARTED = 'FILE_LIST_STARTED'
export const FILE_LIST_COMPLETED = 'FILE_LIST_COMPLETED' export const FILE_LIST_COMPLETED = 'FILE_LIST_COMPLETED'
export const FETCH_FILE_INFO_STARTED = 'FETCH_FILE_INFO_STARTED' export const FETCH_FILE_INFO_STARTED = 'FETCH_FILE_INFO_STARTED'

View file

@ -96,26 +96,27 @@ lbry.call = function (method, params, callback, errorCallback, connectFailedCall
lbry._connectPromise = null; lbry._connectPromise = null;
lbry.connect = function() { lbry.connect = function() {
if (lbry._connectPromise === null) { if (lbry._connectPromise === null) {
lbry._connectPromise = new Promise((resolve, reject) => { lbry._connectPromise = new Promise((resolve, reject) => {
let tryNum = 0
function checkDaemonStartedFailed() {
console.log('status error try num ' + tryNum)
if (tryNum <= 100) { // Move # of tries into constant or config option
setTimeout(() => {
tryNum++
checkDaemonStarted();
}, tryNum < 50 ? 400 : 1000);
}
else {
reject(new Error("Unable to connect to LBRY"));
}
}
// Check every half second to see if the daemon is accepting connections // Check every half second to see if the daemon is accepting connections
function checkDaemonStarted(tryNum = 0) { function checkDaemonStarted() {
lbry.isDaemonAcceptingConnections(function (runningStatus) { console.log('check daemon started try ' + tryNum)
if (runningStatus) { lbry.call('status', {}, resolve, checkDaemonStartedFailed, checkDaemonStartedFailed)
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();
@ -125,11 +126,6 @@ lbry.connect = function() {
return lbry._connectPromise; return lbry._connectPromise;
} }
lbry.isDaemonAcceptingConnections = function (callback) {
// Returns true/false whether the daemon is at a point it will start returning status
lbry.call('status', {}, () => callback(true), null, () => callback(false))
};
lbry.checkAddressIsMine = function(address, callback) { lbry.checkAddressIsMine = function(address, callback) {
lbry.call('address_is_mine', {address: address}, callback); lbry.call('address_is_mine', {address: address}, callback);
} }

View file

@ -96,7 +96,7 @@ var init = function() {
if (window.sessionStorage.getItem('loaded') == 'y') { if (window.sessionStorage.getItem('loaded') == 'y') {
onDaemonReady(); onDaemonReady();
} else { } else {
ReactDOM.render(<SplashScreen message={__("Connecting")} onLoadDone={onDaemonReady} />, canvas); ReactDOM.render(<SplashScreen onLoadDone={onDaemonReady} />, canvas);
} }
}; };

View file

@ -33,6 +33,10 @@ class DiscoverPage extends React.Component{
featuredUris, featuredUris,
fetchingFeaturedUris, fetchingFeaturedUris,
} = this.props } = this.props
const failedToLoad = !fetchingFeaturedUris && (
featuredUris === undefined ||
(featuredUris !== undefined && Object.keys(featuredUris).length === 0)
)
return ( return (
<main> <main>
@ -47,7 +51,7 @@ class DiscoverPage extends React.Component{
)) ))
} }
{ {
typeof featuredUris !== undefined && failedToLoad &&
<div className="empty">{__("Failed to load landing content.")}</div> <div className="empty">{__("Failed to load landing content.")}</div>
} }
</main> </main>

View file

@ -40,20 +40,33 @@ reducers[types.RESOLVE_URI_CANCELED] = function(state, action) {
} }
reducers[types.CLAIM_LIST_MINE_STARTED] = function(state, action) { reducers[types.FETCH_CLAIM_LIST_MINE_STARTED] = function(state, action) {
return Object.assign({}, state, { return Object.assign({}, state, {
isClaimListMinePending: true isClaimListMinePending: true
}) })
} }
reducers[types.CLAIM_LIST_MINE_COMPLETED] = function(state, action) { reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) {
const myClaims = Object.assign({}, state.myClaims) const {
action.data.claims.forEach((claim) => { claims,
myClaims[claim.claim_id] = claim } = action.data
const myClaims = new Set(state.myClaims)
const byUri = Object.assign({}, state.claimsByUri)
claims.forEach(claim => {
const uri = lbryuri.build({
contentName: claim.name,
claimId: claim.claim_id,
claimSequence: claim.nout,
})
myClaims.add(uri)
byUri[uri] = claim
}) })
return Object.assign({}, state, { return Object.assign({}, state, {
isClaimListMinePending: false, isClaimListMinePending: false,
myClaims: myClaims myClaims: myClaims,
claimsByUri: byUri,
}) })
} }

View file

@ -18,7 +18,9 @@ reducers[types.FILE_LIST_COMPLETED] = function(state, action) {
const newFileInfos = Object.assign({}, state.fileInfos) const newFileInfos = Object.assign({}, state.fileInfos)
fileInfos.forEach((fileInfo) => { fileInfos.forEach((fileInfo) => {
newFileInfos[fileInfo.outpoint] = fileInfo const { outpoint } = fileInfo
if (outpoint) newFileInfos[fileInfo.outpoint] = fileInfo
}) })
return Object.assign({}, state, { return Object.assign({}, state, {

View file

@ -113,7 +113,7 @@ export const selectUpgradeFilename = createSelector(
return `LBRY-${version}.dmg`; return `LBRY-${version}.dmg`;
case 'linux': case 'linux':
return `LBRY_${version}_amd64.deb`; return `LBRY_${version}_amd64.deb`;
case 'windows': case 'win32':
return `LBRY.Setup.${version}.exe`; return `LBRY.Setup.${version}.exe`;
default: default:
throw 'Unknown platform'; throw 'Unknown platform';

View file

@ -42,7 +42,8 @@ const selectMetadataForUri = (state, props) => {
const claim = selectClaimForUri(state, props) const claim = selectClaimForUri(state, props)
const metadata = claim && claim.value && claim.value.stream && claim.value.stream.metadata const metadata = claim && claim.value && claim.value.stream && claim.value.stream.metadata
return metadata ? metadata : (claim === undefined ? undefined : null) const value = metadata ? metadata : (claim === undefined ? undefined : null)
return value
} }
export const makeSelectMetadataForUri = () => { export const makeSelectMetadataForUri = () => {
@ -80,18 +81,20 @@ export const selectClaimListMineIsPending = createSelector(
export const selectMyClaims = createSelector( export const selectMyClaims = createSelector(
_selectState, _selectState,
(state) => state.myClaims || {} (state) => state.myClaims || new Set()
) )
export const selectMyClaimsOutpoints = createSelector( export const selectMyClaimsOutpoints = createSelector(
selectMyClaims, selectMyClaims,
(claims) => { selectClaimsByUri,
if (!claims) { (claimIds, byUri) => {
return [] const outpoints = []
}
return Object.values(claims).map((claim) => { claimIds.forEach(claimId => {
return `${claim.txid}:${claim.nout}` const claim = byUri[claimId]
if (claim) outpoints.push(`${claim.txid}:${claim.nout}`)
}) })
return outpoints
} }
) )

View file

@ -1,6 +1,6 @@
{ {
"name": "lbry-web-ui", "name": "lbry-web-ui",
"version": "0.11.5", "version": "0.11.7",
"description": "LBRY UI", "description": "LBRY UI",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
@ -22,9 +22,9 @@
"babel-cli": "^6.11.4", "babel-cli": "^6.11.4",
"babel-preset-es2015": "^6.13.2", "babel-preset-es2015": "^6.13.2",
"babel-preset-react": "^6.11.1", "babel-preset-react": "^6.11.1",
"from2": "^2.3.0",
"jshashes": "^1.0.6", "jshashes": "^1.0.6",
"node-sass": "^3.8.0", "node-sass": "^3.8.0",
"plyr": "^2.0.12",
"rc-progress": "^2.0.6", "rc-progress": "^2.0.6",
"react": "^15.4.0", "react": "^15.4.0",
"react-dom": "^15.4.0", "react-dom": "^15.4.0",
@ -33,6 +33,7 @@
"redux": "^3.6.0", "redux": "^3.6.0",
"redux-logger": "^3.0.1", "redux-logger": "^3.0.1",
"redux-thunk": "^2.2.0", "redux-thunk": "^2.2.0",
"render-media": "^2.10.0",
"reselect": "^3.0.0" "reselect": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {

View file

@ -42,7 +42,11 @@ module.exports = {
cacheDirectory: true, cacheDirectory: true,
presets:[ 'es2015', 'react', 'stage-2' ] presets:[ 'es2015', 'react', 'stage-2' ]
} }
} },
{
test: /mime\.json$/,
loader: 'json',
},
] ]
}, },
target: 'electron-main', target: 'electron-main',

View file

@ -48,7 +48,11 @@ module.exports = {
cacheDirectory: true, cacheDirectory: true,
presets:[ 'es2015', 'react', 'stage-2' ] presets:[ 'es2015', 'react', 'stage-2' ]
} }
} },
{
test: /mime\.json$/,
loader: 'json',
},
] ]
}, },
target: 'electron-main', target: 'electron-main',