Flesh out torrent detail view
This commit is contained in:
parent
b6fd35c5f5
commit
5261b475c1
|
@ -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
24
src/reducers/files.js
Normal 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;
|
||||
}
|
|
@ -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
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" : ""
|
||||
|
|
Loading…
Reference in New Issue
Block a user