Flesh out torrent detail view

This commit is contained in:
Drew DeVault 2017-08-24 22:32:42 -04:00
parent b6fd35c5f5
commit 5261b475c1
5 changed files with 175 additions and 12 deletions

View File

@ -88,3 +88,18 @@
}
}
}
.btn-very-sm {
padding: 0 0.75rem;
line-height: 1;
}
.btn-group {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
.btn-group {
margin-bottom: 0;
}
}

24
src/reducers/files.js Normal file
View File

@ -0,0 +1,24 @@
import {
UPDATE_RESOURCES,
RESOURCES_REMOVED
} from '../actions/resources';
export default function files(state = {}, action) {
switch (action.type) {
case UPDATE_RESOURCES:
return {
...state,
...action.resources
.filter(r => r.type === "file")
.reduce((s, r) => ({
...s,
[r.id]: { ...state[r.id], ...r }
}), {})
};
case RESOURCES_REMOVED:
return Object.values(state)
.filter(r => action.ids.indexOf(r.id) === -1)
.reduce((s, r) => ({ ...s, [r.id]: r }), {});
}
return state;
}

View File

@ -3,11 +3,13 @@ import { routerReducer } from 'react-router-redux'
import subscribe from './subscribe';
import filter_subscribe from './filter_subscribe';
import torrents from './torrents';
import files from './files';
const root = combineReducers({
subscribe,
filter_subscribe,
torrents,
files,
router: routerReducer
});

View File

@ -1,26 +1,140 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { activeTorrents } from '../torrent_state';
import FontAwesome from 'react-fontawesome';
import {
Button,
ButtonGroup,
ButtonDropdown,
DropdownToggle,
DropdownMenu,
DropdownItem
DropdownItem,
Collapse,
Card,
CardBlock,
Progress
} from 'reactstrap';
import ws_send from '../socket';
// TODO: use component lifecycle functions here to invoke
// torrent_state.updateSubscriptions
// TODO: fix navigating directly to torrent pages
function Torrent(props) {
function File({ file }) {
// TODO: show progress bar
// TODO: edit priority
return (
<div>
<h3>{props.torrent.name}</h3>
</div>
<tr>
<td>{file.path}</td>
<td>{file.priority}</td>
<td>{file.availability}</td>
</tr>
);
}
// TODO: move to separate component
function CollapseToggle({ text, onToggle, open }) {
return (
<button
className="btn btn-sm btn-default"
onClick={onToggle}
>
{text}
<FontAwesome
name={`chevron-${open ? "up" : "down"}`}
style={{marginLeft: "0.25rem"}}
/>
</button>
);
}
class Torrent extends Component {
constructor() {
super();
this.state = {
infoShown: false,
filesShown: false,
trackersShown: false,
peersShown: false
};
}
toggleTorrentState(torrent) {
if (torrent.status === "paused") {
ws_send("RESUME_TORRENT", { id: torrent.id });
} else {
ws_send("PAUSE_TORRENT", { id: torrent.id });
}
}
render() {
const { torrent, files } = this.props;
const status = s => s[0].toUpperCase() + s.slice(1);
return (
<div>
<h3>{torrent.name}</h3>
<Progress value={torrent.progress * 100} style={{marginBottom: "0.5rem"}}>
{(torrent.progress * 100).toFixed(0)}%
</Progress>
<p>
<strong>State:</strong>
<span style={{marginLeft: "1rem"}}>
{status(torrent.status)}
</span>
<button
className="btn btn-sm btn-very-sm btn-info"
style={{marginLeft: "1rem"}}
onClick={() => this.toggleTorrentState(torrent)}
>
{torrent.status === "paused" ? "Resume" : "Pause"}
</button>
</p>
<ButtonGroup>
<CollapseToggle
text="Info"
onToggle={() => this.setState({ infoShown: !this.state.infoShown })}
open={this.state.infoShown}
/>
<CollapseToggle
text="Files"
onToggle={() => this.setState({ filesShown: !this.state.filesShown })}
open={this.state.filesShown}
/>
<CollapseToggle
text="Trackers"
onToggle={() => this.setState({ trackersShown: !this.state.trackersShown })}
open={this.state.trackersShown}
/>
<CollapseToggle
text="Peers"
onToggle={() => this.setState({ peersShown: !this.state.peersShown })}
open={this.state.peersShown}
/>
</ButtonGroup>
<Collapse isOpen={this.state.infoShown}>
<dl>
<dt>Downloading to</dt>
<dd>{torrent.path}</dd>
<dt>Created</dt>
<dd>{torrent.created}</dd>
</dl>
</Collapse>
<Collapse isOpen={this.state.filesShown}>
<Card style={{marginBottom: "1rem"}}>
<CardBlock>
<table className="table table-striped" style={{marginBottom: "0"}}>
<tbody>
{files.map(file => <File file={file} />)}
</tbody>
</table>
</CardBlock>
</Card>
</Collapse>
</div>
);
}
}
class TorrentDetails extends Component {
constructor() {
super();
@ -34,13 +148,13 @@ class TorrentDetails extends Component {
<div>
<h3>{active.length} torrents</h3>
<ButtonGroup>
<Button color="info">Pause all</Button>{' '}
<Button color="success">Resume all</Button>{' '}
<button className="btn btn-default btn-sm">Pause all</button>
<button className="btn btn-default btn-sm">Resume all</button>
<ButtonDropdown
isOpen={this.state.removeDropdown}
toggle={() => this.setState({ removeDropdown: !this.state.removeDropdown })}
>
<DropdownToggle color="danger" caret>
<DropdownToggle color="default" caret>
Remove
</DropdownToggle>
<DropdownMenu>
@ -56,10 +170,17 @@ class TorrentDetails extends Component {
render() {
const active = activeTorrents();
const { torrents } = this.props;
const { files } = this.props;
const _files = Object.values(files).reduce((s, f) => ({
...s, [f.torrent_id]: [...(s[f.torrent_id] || []), f]
}), {});
return (
<div>
{active.length > 1 ? this.renderHeader(active) : null}
{active.map(id => <Torrent torrent={torrents[id]} />)}
{active.map(id => <Torrent
torrent={torrents[id]}
files={_files[id] || []}
/>)}
</div>
);
}
@ -67,5 +188,6 @@ class TorrentDetails extends Component {
export default connect(state => ({
router: state.router,
torrents: state.torrents
torrents: state.torrents,
files: state.files
}))(TorrentDetails);

View File

@ -33,7 +33,7 @@ class TorrentTable extends Component {
{Object.values(torrents).map(t =>
<tr
key={t.id}
className={`torrent ${
className={`torrent progress-row ${
t.status
} ${
active.indexOf(t.id) !== -1 ? "selected" : ""