import React, { Component } from 'react'; import { connect } from 'react-redux'; import FontAwesome from 'react-fontawesome'; import { ButtonGroup, ButtonDropdown, DropdownToggle, DropdownMenu, DropdownItem, Collapse, Card, CardBlock, Progress, Input, } from 'reactstrap'; import moment from 'moment'; import TorrentOptions from './torrent_options'; import TorrentProgress from './torrent_progress'; import ws_send from '../socket'; import store from '../store'; import DateDisplay from './date'; import selectTorrent, { EXCLUSIVE, UNION, SUBTRACT, NONE } from '../actions/selection'; import { updateResource } from '../actions/resources'; import { formatBitrate } from '../bitrate'; const dlURI = (uri, token, id) => `${uri.replace('ws', 'http')}/dl/${id}?token=${encodeURIComponent(token)}`; function basename(path) { const parts = path.split("/"); return parts[parts.length - 1]; } class File extends Component { shouldComponentUpdate(nextProps, _) { return nextProps.file !== this.props.file || nextProps.server !== this.props.server || nextProps.socket !== this.props.socket; } render() { const { dispatch, file, server, socket } = this.props; const { download_token } = server; const { uri } = socket; return (
{file.progress === 1.0 ? "done" : `${Math.floor(file.progress * 100)}%`}
{file.progress === 1.0 ? {basename(file.path)} : basename(file.path)}
dispatch(updateResource({ id: file.id, priority: parseInt(e.target.value) }))} >
); } } class Peer extends Component { shouldComponentUpdate(nextProps, _) { return nextProps.peer !== this.props.peer; } render() { const { peer } = this.props; return (
{peer.ip}
{formatBitrate(peer.rate_up)} up
{formatBitrate(peer.rate_down)} down
has {`${(peer.availability * 100).toFixed(0)}%`}
); } } // TODO: move to separate component function CollapseToggle({ text, onToggle, open }) { return ( ); } class Torrent extends Component { constructor() { super(); this.state = { infoShown: false, filesShown: false, trackersShown: false, peersShown: false, removeDropdown: false }; } shouldComponentUpdate(nextProps, nextState) { return nextProps.torrent !== this.props.torrent || nextState.removeDropdown !== this.state.removeDropdown || nextState.peersShown !== this.state.peersShown || nextState.trackersShown !== this.state.trackersShown || nextState.filesShown !== this.state.filesShown || nextState.infoShown !== this.state.infoShown; } toggleTorrentState(torrent) { if (torrent.status === "paused") { ws_send("RESUME_TORRENT", { id: torrent.id }); } else { ws_send("PAUSE_TORRENT", { id: torrent.id }); } } render() { const { dispatch, torrent, files, trackers, peers, server, socket, } = this.props; const status = s => s[0].toUpperCase() + s.slice(1); if (!torrent || !files) { return (

Loading...

); } return (

{torrent.name}

{ e.preventDefault(); this.toggleTorrentState(torrent); }} >
{status(torrent.status)}
this.setState({ removeDropdown: !this.state.removeDropdown })} > Remove { dispatch(selectTorrent([torrent.id], SUBTRACT)); ws_send("REMOVE_RESOURCE", { id: torrent.id }); }} >Remove { dispatch(selectTorrent([torrent.id], SUBTRACT)); ws_send("REMOVE_RESOURCE", { id: torrent.id, artifacts: true }); }} >Remove and delete files
this.setState({ infoShown: !this.state.infoShown })} open={this.state.infoShown} /> this.setState({ filesShown: !this.state.filesShown })} open={this.state.filesShown} /> this.setState({ trackersShown: !this.state.trackersShown })} open={this.state.trackersShown} /> this.setState({ peersShown: !this.state.peersShown })} open={this.state.peersShown} />
Type
{torrent.private ? "Private" : "Public"}
Downloading to
{torrent.path}
Created
Comment
{torrent.comment || "None"}
Creator
{torrent.creator || "None"}
dispatch(updateResource({ id: torrent.id, priority }))} strategyChanged={strategy => dispatch(updateResource({ id: torrent.id, strategy }))} downloadThrottle={torrent.throttle_down} downloadThrottleChanged={throttle_down => dispatch(updateResource({ id: torrent.id, throttle_down }))} uploadThrottle={torrent.throttle_up} uploadThrottleChanged={throttle_up => dispatch(updateResource({ id: torrent.id, throttle_up }))} />
{this.state.filesShown ? files.slice() .sort((a, b) => a.path.localeCompare(b.path)) .map(file => ) : null}
{trackers.map(tracker =>
{(() => { const a = document.createElement("a"); a.href = tracker.url; return a.hostname; })()}
URL
{tracker.url}
Last report
{tracker.error &&
Error
} {tracker.error &&
{tracker.error}
}
)}
{this.state.peersShown ? peers.map(peer => ) : null}
{peers.length === 0 &&

No connected peers.

}
); } } class TorrentDetails extends Component { constructor() { super(); this.state = { removeDropdown: false }; } componentDidMount() { const { dispatch } = this.props; const { ids } = this.props.match.params; const _ids = ids.split(","); dispatch(selectTorrent(_ids, UNION)); } componentWillUnmount() { const { dispatch } = this.props; dispatch(selectTorrent([], EXCLUSIVE, false)); } renderHeader() { const { dispatch, selection } = this.props; return (

{selection.length} torrents
{ e.preventDefault(); selection.forEach(id => ws_send("RESUME_TORRENT", { id })); }} > { e.preventDefault(); selection.forEach(id => ws_send("PAUSE_TORRENT", { id })); }} > this.setState({ removeDropdown: !this.state.removeDropdown })} > Remove all { dispatch(selectTorrent(selection, SUBTRACT)); selection.forEach(id => ws_send("REMOVE_RESOURCE", { id })); }} >Remove selected torrents { dispatch(selectTorrent(selection, SUBTRACT)); selection.forEach(id => ws_send("REMOVE_RESOURCE", { id, artifacts: true })); }} >Remove selected torrents and delete files

); } render() { const { torrents, files, trackers, peers, selection, server, socket, dispatch } = this.props; const index_by_tid = (res) => { let _indexed = {}; Object.values(res).map(r => { if (!(r.torrent_id in _indexed)) { _indexed[r.torrent_id] = []; } _indexed[r.torrent_id].push(r); }); return _indexed; }; const _files = index_by_tid(files); const _trackers = index_by_tid(trackers); const _peers = index_by_tid(peers); return (
{selection.length > 1 ? this.renderHeader.bind(this)() : null} {selection.slice(0, 3).map(id => )} {selection.length > 3 ?

...{selection.length - 3} more hidden...

: null}
); } } export default connect(state => ({ router: state.router, torrents: state.torrents, files: state.files, trackers: state.trackers, peers: state.peers, selection: state.selection, server: state.server, socket: state.socket, }))(TorrentDetails);