3b428c394c
This covers *almost* everything. There are a couple of places that still use names or SD hashes because the APIs haven't been updated yet.
300 lines
9.5 KiB
JavaScript
300 lines
9.5 KiB
JavaScript
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 FormField from './form.js';
|
|
import {ToolTip} from '../component/tooltip.js';
|
|
import {DropDownMenu, DropDownMenuItem} from './menu.js';
|
|
|
|
const {shell} = require('electron');
|
|
|
|
let WatchLink = React.createClass({
|
|
propTypes: {
|
|
streamName: React.PropTypes.string,
|
|
downloadStarted: React.PropTypes.bool,
|
|
},
|
|
startVideo: function() {
|
|
window.location = '?watch=' + this.props.streamName;
|
|
},
|
|
handleClick: function() {
|
|
this.setState({
|
|
loading: true,
|
|
});
|
|
|
|
if (this.props.downloadStarted) {
|
|
this.startVideo();
|
|
} else {
|
|
lbry.getCostInfoForName(this.props.streamName, ({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 (
|
|
<div className="button-set-item">
|
|
<Link button="primary" disabled={this.state.loading} label="Watch" icon="icon-play" onClick={this.handleClick} />
|
|
<Modal contentLabel="Not enough credits" isOpen={this.state.modal == 'notEnoughCredits'} onConfirmed={this.closeModal}>
|
|
You don't have enough LBRY credits to pay for this stream.
|
|
</Modal>
|
|
</div>
|
|
);
|
|
}
|
|
});
|
|
|
|
let FileActionsRow = React.createClass({
|
|
_isMounted: false,
|
|
_fileInfoSubscribeId: null,
|
|
|
|
propTypes: {
|
|
streamName: React.PropTypes.string,
|
|
outpoint: React.PropTypes.string.isRequired,
|
|
metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]),
|
|
},
|
|
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.getCostInfoForName(this.props.streamName, ({cost}) => {
|
|
lbry.getBalance((balance) => {
|
|
if (cost > balance) {
|
|
this.setState({
|
|
modal: 'notEnoughCredits',
|
|
attemptingDownload: false,
|
|
});
|
|
} else {
|
|
lbry.getStream(this.props.streamName, (streamInfo) => {
|
|
if (streamInfo === null || typeof streamInfo !== 'object') {
|
|
this.setState({
|
|
modal: 'timedOut',
|
|
attemptingDownload: false,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
},
|
|
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
|
|
});
|
|
},
|
|
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) {
|
|
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} />;
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
{this.props.metadata.content_type && this.props.metadata.content_type.startsWith('video/')
|
|
? <WatchLink streamName={this.props.streamName} downloadStarted={!!this.state.fileInfo} />
|
|
: null}
|
|
{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 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>lbry://{this.props.streamName}</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>{this.props.metadata.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>
|
|
);
|
|
}
|
|
});
|
|
|
|
export let FileActions = React.createClass({
|
|
_isMounted: false,
|
|
_fileInfoSubscribeId: null,
|
|
|
|
propTypes: {
|
|
streamName: React.PropTypes.string,
|
|
outpoint: React.PropTypes.string.isRequired,
|
|
metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]),
|
|
},
|
|
getInitialState: function() {
|
|
return {
|
|
available: true,
|
|
forceShowActions: false,
|
|
fileInfo: null,
|
|
}
|
|
},
|
|
onShowFileActionsRowClicked: function() {
|
|
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);
|
|
lbry.getStreamAvailability(this.props.streamName, (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;
|
|
},
|
|
render: function() {
|
|
const fileInfo = this.state.fileInfo;
|
|
if (fileInfo === null) {
|
|
return null;
|
|
}
|
|
|
|
return (<section className="file-actions">
|
|
{
|
|
fileInfo || this.state.available || this.state.forceShowActions
|
|
? <FileActionsRow outpoint={this.props.outpoint} metadata={this.props.metadata} streamName={this.props.streamName} />
|
|
: <div>
|
|
<div className="button-set-item empty">This file is not currently available.</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" />
|
|
</div>
|
|
}
|
|
</section>);
|
|
}
|
|
});
|