Redux #115

Merged
6ea86b96 merged 57 commits from redux into redux 2017-05-05 22:55:12 +02:00
22 changed files with 635 additions and 334 deletions
Showing only changes of commit 1d16c73cf4 - Show all commits

View file

@ -1,5 +1,8 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import {
selectCurrentUri,
} from 'selectors/app'
export function doFetchUriAvailability(uri) {
return function(dispatch, getState) {
@ -8,14 +11,29 @@ export function doFetchUriAvailability(uri) {
data: { uri }
})
lbry.get_availability({ uri }, (availability) => {
const successCallback = (availability) => {
dispatch({
type: types.FETCH_AVAILABILITY_COMPLETED',
type: types.FETCH_AVAILABILITY_COMPLETED,
data: {
availability,
uri,
}
})
}
const errorCallback = () => {
console.debug('error')
}
lbry.get_availability({ uri }, successCallback, errorCallback)
}
}
export function doFetchCurrentUriAvailability() {
return function(dispatch, getState) {
const state = getState()
const uri = selectCurrentUri(state)
dispatch(doFetchUriAvailability(uri))
}
}

View file

@ -23,6 +23,9 @@ import {
import {
doOpenModal,
} from 'actions/app'
import {
doFetchCostInfoForUri,
} from 'actions/cost_info'
import batchActions from 'util/batchActions'
export function doResolveUri(uri) {
@ -46,6 +49,8 @@ export function doResolveUri(uri) {
certificate,
}
})
dispatch(doFetchCostInfoForUri(uri))
})
}
}

View file

@ -4,11 +4,8 @@ import {
} from 'selectors/app'
import lbry from 'lbry'
export function doFetchCurrentUriCostInfo() {
export function doFetchCostInfoForUri(uri) {
return function(dispatch, getState) {
const state = getState()
const uri = selectCurrentUri(state)
dispatch({
type: types.FETCH_COST_INFO_STARTED,
data: {
@ -28,3 +25,12 @@ export function doFetchCurrentUriCostInfo() {
}
}
export function doFetchCurrentUriCostInfo() {
return function(dispatch, getState) {
const state = getState()
const uri = selectCurrentUri(state)
dispatch(doFetchCostInfoForUri(uri))
}
}

View file

@ -6,6 +6,13 @@ import {
import {
selectCurrentUriClaimOutpoint,
} from 'selectors/claims'
import {
doCloseModal,
} from 'actions/app'
const {
shell,
} = require('electron')
export function doFetchCurrentUriFileInfo() {
return function(dispatch, getState) {
@ -32,3 +39,40 @@ export function doFetchCurrentUriFileInfo() {
})
}
}
export function doOpenFileInShell(fileInfo) {
return function(dispatch, getState) {
shell.openItem(fileInfo.download_path)
}
}
export function doOpenFileInFolder(fileInfo) {
return function(dispatch, getState) {
shell.showItemInFolder(fileInfo.download_path)
}
}
export function doDeleteFile(uri, fileInfo, deleteFromComputer) {
return function(dispatch, getState) {
dispatch({
type: types.DELETE_FILE_STARTED,
data: {
uri,
fileInfo,
deleteFromComputer,
}
})
const successCallback = () => {
dispatch({
type: types.DELETE_FILE_COMPLETED,
data: {
uri,
}
})
dispatch(doCloseModal())
}
lbry.removeFile(fileInfo.outpoint, deleteFromComputer, successCallback)
}
}

View file

@ -82,54 +82,6 @@ export let CreditAmount = React.createClass({
}
});
export let FilePrice = React.createClass({
_isMounted: false,
propTypes: {
uri: React.PropTypes.string.isRequired,
look: React.PropTypes.oneOf(['indicator', 'plain']),
},
getDefaultProps: function() {
return {
look: 'indicator',
}
},
componentWillMount: function() {
this.setState({
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) {
return <span className={`credit-amount credit-amount--${this.props.look}`}>???</span>;
}
return <CreditAmount label={false} amount={this.state.cost} isEstimate={this.state.isEstimate} showFree={true} />
}
});
var addressStyle = {
fontFamily: '"Consolas", "Lucida Console", "Adobe Source Code Pro", monospace',
};

View file

@ -6,16 +6,62 @@ import {
selectObscureNsfw,
selectHidePrice,
selectHasSignature,
selectPlatform,
} from 'selectors/app'
import {
makeSelectFileInfoForUri,
makeSelectDownloadingForUri,
makeSelectLoadingForUri,
} from 'selectors/file_info'
import {
makeSelectAvailabilityForUri,
} from 'selectors/availability'
import {
selectCurrentModal,
} from 'selectors/app'
import {
doCloseModal,
doOpenModal,
} from 'actions/app'
import {
doOpenFileInShell,
doOpenFileInFolder,
doDeleteFile,
} from 'actions/file_info'
import {
doWatchVideo,
} from 'actions/content'
import FileActions from './view'
const select = (state) => ({
obscureNsfw: selectObscureNsfw(state),
hidePrice: selectHidePrice(state),
hasSignature: selectHasSignature(state),
})
const makeSelect = () => {
const selectFileInfoForUri = makeSelectFileInfoForUri()
const selectAvailabilityForUri = makeSelectAvailabilityForUri()
const selectDownloadingForUri = makeSelectDownloadingForUri()
const selectLoadingForUri = makeSelectLoadingForUri()
const perform = {
const select = (state, props) => ({
obscureNsfw: selectObscureNsfw(state),
hidePrice: selectHidePrice(state),
hasSignature: selectHasSignature(state),
fileInfo: selectFileInfoForUri(state, props),
availability: selectAvailabilityForUri(state, props),
platform: selectPlatform(state),
modal: selectCurrentModal(state),
downloading: selectDownloadingForUri(state, props),
loading: selectLoadingForUri(state, props),
})
return select
}
export default connect(select, perform)(FileActions)
const perform = (dispatch) => ({
closeModal: () => dispatch(doCloseModal()),
openInFolder: (fileInfo) => dispatch(doOpenFileInFolder(fileInfo)),
openInShell: (fileInfo) => dispatch(doOpenFileInShell(fileInfo)),
affirmPurchase: () => console.log('affirm purchase'),
deleteFile: (fileInfo, deleteFromComputer) => dispatch(doDeleteFile(fileInfo, deleteFromComputer)),
openModal: (modal) => dispatch(doOpenModal(modal)),
downloadClick: () => dispatch(doWatchVideo()),
})
export default connect(makeSelect, perform)(FileActions)

View file

@ -1,149 +1,66 @@
import React from 'react';
import lbry from 'lbry';
import lbryuri from 'lbryuri';
import {Icon, FilePrice} from 'component/common';
import {Icon,} from 'component/common';
import FilePrice from 'component/filePrice'
import {Modal} from 'component/modal';
import {FormField} from 'component/form';
import Link from 'component/link';
import {ToolTip} from 'component/tooltip';
import {DropDownMenu, DropDownMenuItem} from 'component/menu';
const {shell} = require('electron');
const FileActionsRow = React.createClass({
_isMounted: false,
_fileInfoSubscribeId: null,
propTypes: {
uri: React.PropTypes.string,
outpoint: React.PropTypes.string.isRequired,
metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]),
contentType: React.PropTypes.string.isRequired,
},
getInitialState: function() {
return {
fileInfo: null,
modal: null,
menuOpen: false,
class FileActionsRow extends React.Component {
constructor(props) {
super(props)
this.state = {
deleteChecked: false,
attemptingDownload: false,
attemptingRemove: false,
}
},
onFileInfoUpdate: function(fileInfo) {
if (this._isMounted) {
this.setState({
fileInfo: fileInfo ? fileInfo : false,
attemptingDownload: fileInfo ? false : this.state.attemptingDownload
});
}
},
tryDownload: function() {
this.setState({
attemptingDownload: true,
attemptingRemove: false
});
lbry.getCostInfo(this.props.uri).then(({cost}) => {
lbry.getBalance((balance) => {
if (cost > balance) {
this.setState({
modal: 'notEnoughCredits',
attemptingDownload: false,
});
} else if (this.state.affirmedPurchase) {
lbry.get({uri: this.props.uri}).then((streamInfo) => {
if (streamInfo === null || typeof streamInfo !== 'object') {
this.setState({
modal: 'timedOut',
attemptingDownload: false,
});
}
});
} else {
this.setState({
attemptingDownload: false,
modal: 'affirmPurchase'
})
}
});
});
},
closeModal: function() {
this.setState({
modal: null,
})
},
onDownloadClick: function() {
if (!this.state.fileInfo && !this.state.attemptingDownload) {
this.tryDownload();
}
},
onOpenClick: function() {
if (this.state.fileInfo && this.state.fileInfo.download_path) {
shell.openItem(this.state.fileInfo.download_path);
}
},
handleDeleteCheckboxClicked: function(event) {
}
handleDeleteCheckboxClicked(event) {
this.setState({
deleteChecked: event.target.checked,
});
},
handleRevealClicked: function() {
if (this.state.fileInfo && this.state.fileInfo.download_path) {
shell.showItemInFolder(this.state.fileInfo.download_path);
}
},
handleRemoveClicked: function() {
this.setState({
modal: 'confirmRemove',
});
},
handleRemoveConfirmed: function() {
lbry.removeFile(this.props.outpoint, this.state.deleteChecked);
this.setState({
modal: null,
fileInfo: false,
attemptingDownload: false
});
},
onAffirmPurchase: function() {
this.setState({
affirmedPurchase: true,
modal: null
});
this.tryDownload();
},
openMenu: function() {
this.setState({
menuOpen: !this.state.menuOpen,
});
},
componentDidMount: function() {
this._isMounted = true;
this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate);
},
componentWillUnmount: function() {
this._isMounted = false;
if (this._fileInfoSubscribeId) {
lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId);
}
},
render: function() {
if (this.state.fileInfo === null)
})
}
render() {
const {
fileInfo,
platform,
downloading,
loading,
uri,
deleteFile,
openInFolder,
openInShell,
modal,
openModal,
affirmPurchase,
closeModal,
downloadClick,
} = this.props
const {
deleteChecked,
} = this.state
const metadata = fileInfo ? fileInfo.metadata : null
if (!fileInfo)
{
return null;
}
const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder',
showMenu = !!this.state.fileInfo;
const openInFolderMessage = platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder',
showMenu = Object.keys(fileInfo).length != 0;
let linkBlock;
if (this.state.fileInfo === false && !this.state.attemptingDownload) {
linkBlock = <Link button="text" label="Download" icon="icon-download" onClick={this.onDownloadClick} />;
} else if (this.state.attemptingDownload || (!this.state.fileInfo.completed && !this.state.fileInfo.isMine)) {
if (Object.keys(fileInfo).length == 0 && !downloading && !loading) {
linkBlock = <Link button="text" label="Download" icon="icon-download" onClick={downloadClick} />;
} else if (downloading || loading) {
const
progress = this.state.fileInfo ? this.state.fileInfo.written_bytes / this.state.fileInfo.total_bytes * 100 : 0,
label = this.state.fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...',
progress = (fileInfo && fileInfo.written_bytes) ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0,
label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...',
labelWithIcon = <span className="button__content"><Icon icon="icon-download" /><span>{label}</span></span>;
linkBlock = (
@ -153,101 +70,253 @@ const FileActionsRow = React.createClass({
</div>
);
} else {
linkBlock = <Link label="Open" button="text" icon="icon-folder-open" onClick={this.onOpenClick} />;
linkBlock = <Link label="Open" button="text" icon="icon-folder-open" onClick={() => openInShell(fileInfo)} />;
}
const uri = lbryuri.normalize(this.props.uri);
const title = this.props.metadata ? this.props.metadata.title : uri;
const title = metadata ? metadata.title : uri;
return (
<div>
{this.state.fileInfo !== null || this.state.fileInfo.isMine
{fileInfo !== null || fileInfo.isMine
? linkBlock
: null}
{ showMenu ?
<DropDownMenu>
<DropDownMenuItem key={0} onClick={this.handleRevealClicked} label={openInFolderMessage} />
<DropDownMenuItem key={1} onClick={this.handleRemoveClicked} label="Remove..." />
<DropDownMenuItem key={0} onClick={() => openInFolder(fileInfo)} label={openInFolderMessage} />
<DropDownMenuItem key={1} onClick={() => openModal('confirmRemove')} label="Remove..." />
</DropDownMenu> : '' }
<Modal type="confirm" isOpen={this.state.modal == 'affirmPurchase'}
contentLabel="Confirm Purchase" onConfirmed={this.onAffirmPurchase} onAborted={this.closeModal}>
Are you sure you'd like to buy <strong>{title}</strong> for <strong><FilePrice uri={uri} metadata={this.props.metadata} label={false} look="plain" /></strong> credits?
<Modal type="confirm" isOpen={modal == 'affirmPurchase'}
contentLabel="Confirm Purchase" onConfirmed={affirmPurchase} onAborted={closeModal}>
Are you sure you'd like to buy <strong>{title}</strong> for <strong><FilePrice uri={uri} look="plain" /></strong> credits?
</Modal>
<Modal isOpen={this.state.modal == 'notEnoughCredits'} contentLabel="Not enough credits"
onConfirmed={this.closeModal}>
<Modal isOpen={modal == 'notEnoughCredits'} contentLabel="Not enough credits"
onConfirmed={closeModal}>
You don't have enough LBRY credits to pay for this stream.
</Modal>
<Modal isOpen={this.state.modal == 'timedOut'} contentLabel="Download failed"
onConfirmed={this.closeModal}>
<Modal isOpen={modal == 'timedOut'} contentLabel="Download failed"
onConfirmed={closeModal}>
LBRY was unable to download the stream <strong>{uri}</strong>.
</Modal>
<Modal isOpen={this.state.modal == 'confirmRemove'} contentLabel="Not enough credits"
type="confirm" confirmButtonLabel="Remove" onConfirmed={this.handleRemoveConfirmed}
onAborted={this.closeModal}>
<Modal isOpen={modal == 'confirmRemove'}
contentLabel="Not enough credits"
type="confirm"
confirmButtonLabel="Remove"
onConfirmed={() => deleteFile(uri, fileInfo, deleteChecked)}
onAborted={closeModal}>
<p>Are you sure you'd like to remove <cite>{title}</cite> from LBRY?</p>
<label><FormField type="checkbox" checked={this.state.deleteChecked} onClick={this.handleDeleteCheckboxClicked} /> Delete this file from my computer</label>
<label><FormField type="checkbox" checked={deleteChecked} onClick={this.handleDeleteCheckboxClicked.bind(this)} /> Delete this file from my computer</label>
</Modal>
</div>
);
}
});
}
const FileActions = React.createClass({
_isMounted: false,
_fileInfoSubscribeId: null,
// const FileActionsRow = React.createClass({
// _isMounted: false,
// _fileInfoSubscribeId: null,
propTypes: {
uri: React.PropTypes.string,
outpoint: React.PropTypes.string.isRequired,
metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]),
contentType: React.PropTypes.string,
},
getInitialState: function() {
return {
// propTypes: {
// uri: React.PropTypes.string,
// outpoint: React.PropTypes.string.isRequired,
// metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]),
// contentType: React.PropTypes.string.isRequired,
// },
// getInitialState: function() {
// return {
// fileInfo: null,
// modal: null,
// menuOpen: false,
// deleteChecked: false,
// attemptingDownload: false,
// attemptingRemove: false,
// }
// },
// onFileInfoUpdate: function(fileInfo) {
// if (this._isMounted) {
// this.setState({
// fileInfo: fileInfo ? fileInfo : false,
// attemptingDownload: fileInfo ? false : this.state.attemptingDownload
// });
// }
// },
// tryDownload: function() {
// this.setState({
// attemptingDownload: true,
// attemptingRemove: false
// });
// lbry.getCostInfo(this.props.uri).then(({cost}) => {
// lbry.getBalance((balance) => {
// if (cost > balance) {
// this.setState({
// modal: 'notEnoughCredits',
// attemptingDownload: false,
// });
// } else if (this.state.affirmedPurchase) {
// lbry.get({uri: this.props.uri}).then((streamInfo) => {
// if (streamInfo === null || typeof streamInfo !== 'object') {
// this.setState({
// modal: 'timedOut',
// attemptingDownload: false,
// });
// }
// });
// } else {
// this.setState({
// attemptingDownload: false,
// modal: 'affirmPurchase'
// })
// }
// });
// });
// },
// closeModal: function() {
// this.setState({
// modal: null,
// })
// },
// onDownloadClick: function() {
// if (!this.state.fileInfo && !this.state.attemptingDownload) {
// this.tryDownload();
// }
// },
// onOpenClick: function() {
// if (this.state.fileInfo && this.state.fileInfo.download_path) {
// shell.openItem(this.state.fileInfo.download_path);
// }
// },
// handleDeleteCheckboxClicked: function(event) {
// this.setState({
// deleteChecked: event.target.checked,
// });
// },
// handleRevealClicked: function() {
// if (this.state.fileInfo && this.state.fileInfo.download_path) {
// shell.showItemInFolder(this.state.fileInfo.download_path);
// }
// },
// handleRemoveClicked: function() {
// this.setState({
// modal: 'confirmRemove',
// });
// },
// handleRemoveConfirmed: function() {
// lbry.removeFile(this.props.outpoint, this.state.deleteChecked);
// this.setState({
// modal: null,
// fileInfo: false,
// attemptingDownload: false
// });
// },
// onAffirmPurchase: function() {
// this.setState({
// affirmedPurchase: true,
// modal: null
// });
// this.tryDownload();
// },
// openMenu: function() {
// this.setState({
// menuOpen: !this.state.menuOpen,
// });
// },
// componentDidMount: function() {
// this._isMounted = true;
// this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate);
// },
// componentWillUnmount: function() {
// this._isMounted = false;
// if (this._fileInfoSubscribeId) {
// lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId);
// }
// },
// render: function() {
// if (this.state.fileInfo === null)
// {
// return null;
// }
// const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder',
// showMenu = !!this.state.fileInfo;
// let linkBlock;
// if (this.state.fileInfo === false && !this.state.attemptingDownload) {
// linkBlock = <Link button="text" label="Download" icon="icon-download" onClick={this.onDownloadClick} />;
// } else if (this.state.attemptingDownload || (!this.state.fileInfo.completed && !this.state.fileInfo.isMine)) {
// const
// progress = this.state.fileInfo ? this.state.fileInfo.written_bytes / this.state.fileInfo.total_bytes * 100 : 0,
// label = this.state.fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...',
// labelWithIcon = <span className="button__content"><Icon icon="icon-download" /><span>{label}</span></span>;
// linkBlock = (
// <div className="faux-button-block file-actions__download-status-bar button-set-item">
// <div className="faux-button-block file-actions__download-status-bar-overlay" style={{ width: progress + '%' }}>{labelWithIcon}</div>
// {labelWithIcon}
// </div>
// );
// } else {
// linkBlock = <Link label="Open" button="text" icon="icon-folder-open" onClick={this.onOpenClick} />;
// }
// const uri = lbryuri.normalize(this.props.uri);
// const title = this.props.metadata ? this.props.metadata.title : uri;
// return (
// <div>
// {this.state.fileInfo !== null || this.state.fileInfo.isMine
// ? linkBlock
// : null}
// { showMenu ?
// <DropDownMenu>
// <DropDownMenuItem key={0} onClick={this.handleRevealClicked} label={openInFolderMessage} />
// <DropDownMenuItem key={1} onClick={this.handleRemoveClicked} label="Remove..." />
// </DropDownMenu> : '' }
// <Modal type="confirm" isOpen={this.state.modal == 'affirmPurchase'}
// contentLabel="Confirm Purchase" onConfirmed={this.onAffirmPurchase} onAborted={this.closeModal}>
// Are you sure you'd like to buy <strong>{title}</strong> for <strong><FilePrice uri={uri} look="plain" /></strong> credits?
// </Modal>
// <Modal isOpen={this.state.modal == 'notEnoughCredits'} contentLabel="Not enough credits"
// onConfirmed={this.closeModal}>
// You don't have enough LBRY credits to pay for this stream.
// </Modal>
// <Modal isOpen={this.state.modal == 'timedOut'} contentLabel="Download failed"
// onConfirmed={this.closeModal}>
// LBRY was unable to download the stream <strong>{uri}</strong>.
// </Modal>
// <Modal isOpen={this.state.modal == 'confirmRemove'} contentLabel="Not enough credits"
// type="confirm" confirmButtonLabel="Remove" onConfirmed={this.handleRemoveConfirmed}
// onAborted={this.closeModal}>
// <p>Are you sure you'd like to remove <cite>{title}</cite> from LBRY?</p>
// <label><FormField type="checkbox" checked={this.state.deleteChecked} onClick={this.handleDeleteCheckboxClicked} /> Delete this file from my computer</label>
// </Modal>
// </div>
// );
// }
// });
class FileActions extends React.Component {
constructor(props) {
super(props)
this._isMounted = false
this._fileInfoSubscribeId = null
this.state = {
available: true,
forceShowActions: false,
fileInfo: null,
}
},
onShowFileActionsRowClicked: function() {
}
onShowFileActionsRowClicked() {
this.setState({
forceShowActions: true,
});
},
onFileInfoUpdate: function(fileInfo) {
if (this.isMounted) {
this.setState({
fileInfo: fileInfo,
});
}
},
componentDidMount: function() {
this._isMounted = true;
this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate);
}
render() {
const {
fileInfo,
availability,
} = this.props
lbry.get_availability({uri: this.props.uri}, (availability) => {
if (this._isMounted) {
this.setState({
available: availability > 0,
});
}
}, () => {
// Take any error to mean the file is unavailable
if (this._isMounted) {
this.setState({
available: false,
});
}
});
},
componentWillUnmount: function() {
this._isMounted = false;
if (this._fileInfoSubscribeId) {
lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId);
}
},
render: function() {
const fileInfo = this.state.fileInfo;
if (fileInfo === null) {
return null;
}
@ -255,18 +324,17 @@ const FileActions = React.createClass({
return (<section className="file-actions">
{
fileInfo || this.state.available || this.state.forceShowActions
? <FileActionsRow outpoint={this.props.outpoint} metadata={this.props.metadata} uri={this.props.uri}
contentType={this.props.contentType} />
? <FileActionsRow {...this.props} />
: <div>
<div className="button-set-item empty">Content unavailable.</div>
<ToolTip label="Why?"
body="The content on LBRY is hosted by its users. It appears there are no users connected that have this file at the moment."
className="button-set-item" />
<Link label="Try Anyway" onClick={this.onShowFileActionsRowClicked} className="button-text button-set-item" />
<Link label="Try Anyway" onClick={this.onShowFileActionsRowClicked.bind(this)} className="button-text button-set-item" />
</div>
}
</section>);
}
});
}
export default FileActions

View file

@ -2,7 +2,8 @@ import React from 'react';
import lbry from 'lbry.js';
import lbryuri from 'lbryuri.js';
import Link from 'component/link';
import {Thumbnail, TruncatedText, FilePrice} from 'component/common';
import {Thumbnail, TruncatedText,} from 'component/common';
import FilePrice from 'component/filePrice'
import UriIndicator from 'component/channel-indicator';
class FileCardStream extends React.Component {
@ -72,7 +73,7 @@ class FileCardStream extends React.Component {
<div className="card__title-identity">
<h5 title={title}><TruncatedText lines={1}>{title}</TruncatedText></h5>
<div className="card__subtitle">
{ !this.props.hidePrice ? <span style={{float: "right"}}><FilePrice uri={this.props.uri} /></span> : null}
{ !this.props.hidePrice ? <span style={{float: "right"}}><FilePrice uri={lbryuri.normalize(this.props.uri)} /></span> : null}
<UriIndicator uri={uri} metadata={metadata} contentType={this.props.contentType}
hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} />
</div>

View file

@ -0,0 +1,22 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
makeSelectCostInfoForUri,
} from 'selectors/cost_info'
import FilePrice from './view'
const makeSelect = () => {
const selectCostInfoForUri = makeSelectCostInfoForUri()
const select = (state, props) => ({
costInfo: selectCostInfoForUri(state, props),
})
return select
}
const perform = (dispatch) => ({
})
export default connect(makeSelect, perform)(FilePrice)

View file

@ -0,0 +1,21 @@
import React from 'react'
import {
CreditAmount,
} from 'component/common'
const FilePrice = (props) => {
const {
costInfo,
look = 'indicator',
} = props
const isEstimate = costInfo ? !costInfo.includesData : null
if (!costInfo) {
return <span className={`credit-amount credit-amount--${look}`}>???</span>;
}
return <CreditAmount label={false} amount={costInfo.cost} isEstimate={isEstimate} showFree={true} />
}
export default FilePrice

View file

@ -3,7 +3,8 @@ import lbry from 'lbry.js';
import lbryuri from 'lbryuri.js';
import Link from 'component/link';
import FileActions from 'component/fileActions';
import {Thumbnail, TruncatedText, FilePrice} from 'component/common.js';
import {Thumbnail, TruncatedText,} from 'component/common.js';
import FilePrice from 'component/filePrice'
import UriIndicator from 'component/channel-indicator.js';
/*should be merged into FileTile once FileTile is refactored to take a single id*/

View file

@ -16,8 +16,6 @@ import {
selectLoadingCurrentUri,
selectCurrentUriFileReadyToPlay,
selectCurrentUriIsPlaying,
} from 'selectors/content'
import {
selectCurrentUriFileInfo,
selectDownloadingCurrentUri,
} from 'selectors/file_info'

View file

@ -56,6 +56,8 @@ export const DOWNLOADING_COMPLETED = 'DOWNLOADING_COMPLETED'
export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED'
export const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED'
export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED'
export const DELETE_FILE_STARTED = 'DELETE_FILE_STARTED'
export const DELETE_FILE_COMPLETED = 'DELETE_FILE_COMPLETED'
// Search
export const SEARCH_STARTED = 'SEARCH_STARTED'

View file

@ -301,7 +301,7 @@ lbry.getMyClaims = function(callback) {
lbry.removeFile = function(outpoint, deleteTargetFile=true, callback) {
this._removedFiles.push(outpoint);
this._updateFileInfoSubscribers(outpoint);
// this._updateFileInfoSubscribers(outpoint);
lbry.file_delete({
outpoint: outpoint,

View file

@ -6,9 +6,9 @@ import Video from 'component/video'
import {
TruncatedText,
Thumbnail,
FilePrice,
BusyMessage
} from 'component/common.js';
BusyMessage,
} from 'component/common';
import FilePrice from 'component/filePrice'
import FileActions from 'component/fileActions';
import Link from 'component/link';
import UriIndicator from 'component/channel-indicator.js';
@ -121,7 +121,7 @@ let FilePage = React.createClass({
render: function() {
const metadata = this.props.metadata,
title = metadata ? this.props.metadata.title : this.props.uri,
uriIndicator = <UriIndicator uri={this.props.uri} hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} />;
uriIndicator = <UriIndicator uri={uri} hasSignature={hasSignature} signatureIsValid={signatureIsValid} />
return (
<main className="main--single-column">
@ -134,14 +134,14 @@ let FilePage = React.createClass({
<div className="card__inner">
<div className="card__title-identity">
{this.state.isDownloaded === false
? <span style={{float: "right"}}><FilePrice uri={this.props.uri} metadata={metadata} /></span>
? <span style={{float: "right"}}><FilePrice uri={lbryuri.normalize(uri)} metadata={metadata} /></span>
: null}
<h1>{title}</h1>
<div className="card__subtitle">
{ this.props.channelUri ?
<Link href={"?show=" + this.props.channelUri }>{uriIndicator}</Link> :
uriIndicator}
</div>
</div>
<div className="card__actions">
<FileActions uri={this.props.uri} outpoint={this.props.outpoint} metadata={metadata} contentType={this.props.contentType} />
</div>
@ -289,3 +289,5 @@ let ShowPage = React.createClass({
return <main className="main--single-column">{innerContent}</main>;
}
});
export default ShowPage;

View file

@ -96,36 +96,6 @@ reducers[types.FETCH_PUBLISHED_CONTENT_COMPLETED] = function(state, action) {
})
}
reducers[types.LOADING_VIDEO_STARTED] = function(state, action) {
const {
uri,
} = action.data
const newLoading = Object.assign({}, state.loading)
const newByUri = Object.assign({}, newLoading.byUri)
newByUri[uri] = true
newLoading.byUri = newByUri
return Object.assign({}, state, {
loading: newLoading,
})
}
reducers[types.LOADING_VIDEO_FAILED] = function(state, action) {
const {
uri,
} = action.data
const newLoading = Object.assign({}, state.loading)
const newByUri = Object.assign({}, newLoading.byUri)
delete newByUri[uri]
newLoading.byUri = newByUri
return Object.assign({}, state, {
loading: newLoading,
})
}
export default function reducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);

View file

@ -41,13 +41,20 @@ reducers[types.DOWNLOADING_STARTED] = function(state, action) {
} = action.data
const newByUri = Object.assign({}, state.byUri)
const newDownloading = Object.assign({}, state.downloading)
const newDownloadingByUri = Object.assign({}, newDownloading.byUri)
const newLoading = Object.assign({}, state.loading)
const newLoadingByUri = Object.assign({}, newLoading)
newDownloading[uri] = true
newDownloadingByUri[uri] = true
newDownloading.byUri = newDownloadingByUri
newByUri[uri] = fileInfo
delete newLoadingByUri[uri]
newLoading.byUri = newLoadingByUri
return Object.assign({}, state, {
downloading: newDownloading,
byUri: newByUri,
loading: newLoading,
})
}
@ -75,13 +82,78 @@ reducers[types.DOWNLOADING_COMPLETED] = function(state, action) {
} = action.data
const newByUri = Object.assign({}, state.byUri)
const newDownloading = Object.assign({}, state.downloading)
const newDownloadingByUri = Object.assign({}, newDownloading.byUri)
newByUri[uri] = fileInfo
delete newDownloading[uri]
delete newDownloadingByUri[uri]
newDownloading.byUri = newDownloadingByUri
return Object.assign({}, state, {
byUri: newByUri,
downloading: newDownloading
downloading: newDownloading,
})
}
reducers[types.DELETE_FILE_STARTED] = function(state, action) {
const {
uri,
} = action.data
const newDeleting = Object.assign({}, state.deleting)
const newByUri = Object.assign({}, newDeleting.byUri)
newByUri[uri] = true
newDeleting.byUri = newByUri
return Object.assign({}, state, {
deleting: newDeleting,
})
}
reducers[types.DELETE_FILE_COMPLETED] = function(state, action) {
const {
uri,
} = action.data
const newDeleting = Object.assign({}, state.deleting)
const newDeletingByUri = Object.assign({}, newDeleting.byUri)
const newByUri = Object.assign({}, state.byUri)
delete newDeletingByUri[uri]
newDeleting.byUri = newDeletingByUri
delete newByUri[uri]
return Object.assign({}, state, {
deleting: newDeleting,
byUri: newByUri,
})
}
reducers[types.LOADING_VIDEO_STARTED] = function(state, action) {
const {
uri,
} = action.data
const newLoading = Object.assign({}, state.loading)
const newByUri = Object.assign({}, newLoading.byUri)
newByUri[uri] = true
newLoading.byUri = newByUri
return Object.assign({}, state, {
loading: newLoading,
})
}
reducers[types.LOADING_VIDEO_FAILED] = function(state, action) {
const {
uri,
} = action.data
const newLoading = Object.assign({}, state.loading)
const newByUri = Object.assign({}, newLoading.byUri)
delete newByUri[uri]
newLoading.byUri = newByUri
return Object.assign({}, state, {
loading: newLoading,
})
}

View file

@ -1,6 +1,11 @@
import {
createSelector,
} from 'reselect'
import {
selectDaemonReady,
selectCurrentPage,
selectCurrentUri,
} from 'selectors/app'
const _selectState = state => state.availability
@ -40,3 +45,30 @@ export const makeSelectFetchingAvailabilityForUri = () => {
(fetching) => fetching
)
}
export const selectFetchingAvailabilityForCurrentUri = createSelector(
selectCurrentUri,
selectFetchingAvailabilityByUri,
(uri, byUri) => byUri[uri]
)
export const selectAvailabilityForCurrentUri = createSelector(
selectCurrentUri,
selectAvailabilityByUri,
(uri, byUri) => byUri[uri]
)
export const shouldFetchCurrentUriAvailability = createSelector(
selectDaemonReady,
selectCurrentPage,
selectFetchingAvailabilityForCurrentUri,
selectAvailabilityForCurrentUri,
(daemonReady, page, fetching, availability) => {
if (!daemonReady) return false
if (page != 'show') return false
if (fetching) return false
if (availability) return false
return true
}
)

View file

@ -4,14 +4,6 @@ import {
selectCurrentPage,
selectCurrentUri,
} from 'selectors/app'
import {
selectCurrentUriCostInfo,
selectFetchingCurrentUriCostInfo,
} from 'selectors/cost_info'
import {
selectCurrentUriFileInfo,
selectFetchingCurrentUriFileInfo,
} from 'selectors/file_info'
export const _selectState = state => state.content || {}
@ -50,13 +42,6 @@ export const selectFetchingFileInfos = createSelector(
(state) => state.fetchingFileInfos || {}
)
// TODO make this smarter so it doesn't start playing and immediately freeze
// while downloading more.
export const selectCurrentUriFileReadyToPlay = createSelector(
selectCurrentUriFileInfo,
(fileInfo) => (fileInfo || {}).written_bytes > 0
)
export const selectFetchingDownloadedContent = createSelector(
_selectState,
(state) => !!state.fetchingDownloadedContent
@ -97,22 +82,6 @@ export const selectPublishedContent = createSelector(
(state) => state.publishedContent || {}
)
export const selectLoading = createSelector(
_selectState,
(state) => state.loading || {}
)
export const selectLoadingByUri = createSelector(
selectLoading,
(loading) => loading.byUri || {}
)
export const selectLoadingCurrentUri = createSelector(
selectLoadingByUri,
selectCurrentUri,
(byUri, uri) => !!byUri[uri]
)
export const shouldFetchPublishedContent = createSelector(
selectDaemonReady,
selectCurrentPage,

View file

@ -42,3 +42,13 @@ export const shouldFetchCurrentUriCostInfo = createSelector(
}
)
const selectCostInfoForUri = (state, props) => {
return selectAllCostInfoByUri(state)[props.uri]
}
export const makeSelectCostInfoForUri = () => {
return createSelector(
selectCostInfoForUri,
(costInfo) => costInfo
)
}

View file

@ -54,7 +54,11 @@ export const selectDownloadingCurrentUri = createSelector(
export const selectCurrentUriIsDownloaded = createSelector(
selectCurrentUriFileInfo,
(fileInfo) => {
return fileInfo && (fileInfo.written_bytes > 0 || fileInfo.completed)
if (!fileInfo) return false
if (!fileInfo.completed) return false
if (!fileInfo.written_bytes > 0) return false
return true
}
)
@ -82,3 +86,50 @@ export const makeSelectFileInfoForUri = () => {
(fileInfo) => fileInfo
)
}
const selectDownloadingForUri = (state, props) => {
const byUri = selectDownloadingByUri(state)
return byUri[props.uri]
}
export const makeSelectDownloadingForUri = () => {
return createSelector(
selectDownloadingForUri,
(downloadingForUri) => !!downloadingForUri
)
}
export const selectLoading = createSelector(
_selectState,
(state) => state.loading || {}
)
export const selectLoadingByUri = createSelector(
selectLoading,
(loading) => loading.byUri || {}
)
export const selectLoadingCurrentUri = createSelector(
selectLoadingByUri,
selectCurrentUri,
(byUri, uri) => !!byUri[uri]
)
// TODO make this smarter so it doesn't start playing and immediately freeze
// while downloading more.
export const selectCurrentUriFileReadyToPlay = createSelector(
selectCurrentUriFileInfo,
(fileInfo) => (fileInfo || {}).written_bytes > 0
)
const selectLoadingForUri = (state, props) => {
const byUri = selectLoadingByUri(state)
return byUri[props.uri]
}
export const makeSelectLoadingForUri = () => {
return createSelector(
selectLoadingForUri,
(loading) => !!loading
)
}

View file

@ -13,6 +13,9 @@ import {
import {
shouldFetchCurrentUriCostInfo,
} from 'selectors/cost_info'
import {
shouldFetchCurrentUriAvailability,
} from 'selectors/availability'
import {
doFetchTransactions,
doGetNewAddress,
@ -28,6 +31,9 @@ import {
import {
doFetchCurrentUriCostInfo,
} from 'actions/cost_info'
import {
doFetchCurrentUriAvailability,
} from 'actions/availability'
const triggers = []
@ -66,6 +72,11 @@ triggers.push({
action: doFetchCurrentUriCostInfo,
})
triggers.push({
selector: shouldFetchCurrentUriAvailability,
action: doFetchCurrentUriAvailability,
})
const runTriggers = function() {
triggers.forEach(function(trigger) {
const state = app.store.getState();